]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Use more informative descriptions fro Springer layouts
[lyx.git] / src / support / filetools.cpp
1 /**
2  * \file filetools.cpp
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 "LyX.h"
25 #include "LyXRC.h"
26
27 #include "support/filetools.h"
28
29 #include "support/convert.h"
30 #include "support/debug.h"
31 #include "support/environment.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/os.h"
35 #include "support/Messages.h"
36 #include "support/Package.h"
37 #include "support/PathChanger.h"
38 #include "support/Systemcall.h"
39 #include "support/qstring_helpers.h"
40 #include "support/TempFile.h"
41
42 #include <QDir>
43
44 #include "support/lassert.h"
45 #include "support/regex.h"
46
47 #include <fcntl.h>
48 #ifdef HAVE_MAGIC_H
49 #include <magic.h>
50 #endif
51 #ifdef HAVE_UNISTD_H
52 #include <unistd.h>
53 #endif
54
55 #include <cerrno>
56 #include <climits>
57 #include <cstdlib>
58 #include <cstdio>
59
60 #include <utility>
61 #include <fstream>
62 #include <sstream>
63 #include <vector>
64
65 #if defined (_WIN32)
66 #include <io.h>
67 #include <windows.h>
68 #endif
69
70 using namespace std;
71
72 #define USE_QPROCESS
73
74 namespace lyx {
75 namespace support {
76
77 bool isLyXFileName(string const & filename)
78 {
79         return suffixIs(ascii_lowercase(filename), ".lyx");
80 }
81
82
83 bool isSGMLFileName(string const & filename)
84 {
85         return suffixIs(ascii_lowercase(filename), ".sgml");
86 }
87
88
89 bool isValidLaTeXFileName(string const & filename)
90 {
91         string const invalid_chars("#%\"");
92         return filename.find_first_of(invalid_chars) == string::npos;
93 }
94
95
96 bool isValidDVIFileName(string const & filename)
97 {
98         string const invalid_chars("${}()[]^");
99         return filename.find_first_of(invalid_chars) == string::npos;
100 }
101
102
103 bool isBinaryFile(FileName const & filename)
104 {
105         bool isbinary = false;
106         if (filename.empty() || !filename.exists())
107                 return isbinary;
108
109 #ifdef HAVE_MAGIC_H
110         magic_t magic_cookie = magic_open(MAGIC_MIME_ENCODING);
111         if (magic_cookie) {
112                 bool detected = true;
113                 if (magic_load(magic_cookie, NULL) != 0) {
114                         LYXERR(Debug::FILES, "isBinaryFile: "
115                                 "Could not load magic database - "
116                                 << magic_error(magic_cookie));
117                         detected = false;
118                 } else {
119                         char const *charset = magic_file(magic_cookie,
120                                         filename.toFilesystemEncoding().c_str());
121                         isbinary = contains(charset, "binary");
122                 }
123                 magic_close(magic_cookie);
124                 if (detected)
125                         return isbinary;
126         }
127 #endif
128         // Try by looking for binary chars at the beginning of the file.
129         // Note that this is formally not correct, since count_bin_chars
130         // expects utf8, and the passed string can be anything: plain text
131         // in any encoding, or really binary data. In practice it works,
132         // since QString::fromUtf8() drops invalid utf8 sequences, and
133         // while the exact number may not be correct, we still get a high
134         // number for truly binary files.
135
136         ifstream ifs(filename.toFilesystemEncoding().c_str());
137         if (!ifs)
138                 return isbinary;
139
140         // Maximum strings to read
141         int const max_count = 50;
142
143         // Maximum number of binary chars allowed
144         int const max_bin = 5;
145
146         int count = 0;
147         int binchars = 0;
148         string str;
149         while (count++ < max_count && !ifs.eof()) {
150                 getline(ifs, str);
151                 binchars += count_bin_chars(str);
152         }
153         return binchars > max_bin;
154 }
155
156
157 string const latex_path(string const & original_path,
158                 latex_path_extension extension,
159                 latex_path_dots dots)
160 {
161         // On cygwin, we may need windows or posix style paths.
162         string path = os::latex_path(original_path);
163         path = subst(path, "~", "\\string~");
164         if (path.find(' ') != string::npos) {
165                 // We can't use '"' because " is sometimes active (e.g. if
166                 // babel is loaded with the "german" option)
167                 if (extension == EXCLUDE_EXTENSION) {
168                         // changeExtension calls os::internal_path internally
169                         // so don't use it to remove the extension.
170                         string const ext = getExtension(path);
171                         string const base = ext.empty() ?
172                                 path :
173                                 path.substr(0, path.length() - ext.length() - 1);
174                         // changeExtension calls os::internal_path internally
175                         // so don't use it to re-add the extension.
176                         path = "\\string\"" + base + "\\string\"." + ext;
177                 } else {
178                         path = "\\string\"" + path + "\\string\"";
179                 }
180         }
181
182         if (dots != ESCAPE_DOTS)
183                 return path;
184
185         // Replace dots with the lyxdot macro, but only in the file name,
186         // not the directory part.
187         // addName etc call os::internal_path internally
188         // so don't use them for path manipulation
189         // The directory separator is always '/' for LaTeX.
190         string::size_type pos = path.rfind('/');
191         if (pos == string::npos)
192                 return subst(path, ".", "\\lyxdot ");
193         return path.substr(0, pos) + subst(path.substr(pos), ".", "\\lyxdot ");
194 }
195
196
197 // Substitutes spaces with underscores in filename (and path)
198 FileName const makeLatexName(FileName const & file)
199 {
200         string name = file.onlyFileName();
201         string const path = file.onlyPath().absFileName() + "/";
202
203         // ok so we scan through the string twice, but who cares.
204         // FIXME: in Unicode time this will break for sure! There is
205         // a non-latin world out there...
206         string const keep = "abcdefghijklmnopqrstuvwxyz"
207                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
208                 "@!'()*+,-./0123456789:;<=>?[]`|";
209
210         string::size_type pos = 0;
211         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
212                 name[pos++] = '_';
213
214         FileName latex_name(path + name);
215         latex_name.changeExtension(".tex");
216         return latex_name;
217 }
218
219
220 string const quoteName(string const & name, quote_style style)
221 {
222         switch(style) {
223         case quote_shell:
224                 // This does not work on native Windows for filenames
225                 // containing the following characters < > : " / \ | ? *
226                 // Moreover, it can't be made to work, as, according to
227                 // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
228                 // those are reserved characters, and thus are forbidden.
229                 // Please, also note that the command-line parser in
230                 // ForkedCall::generateChild cannot deal with filenames
231                 // containing " or ', therefore we don't pass user filenames
232                 // to child processes if possible. We store them in a python
233                 // script instead, where we don't have these limitations.
234 #ifndef USE_QPROCESS
235                 return (os::shell() == os::UNIX) ?
236                         '\'' + subst(name, "'", "\'\\\'\'") + '\'' :
237                         '"' + name + '"';
238 #else
239                 // According to the QProcess parser, a single double
240                 // quote is represented by three consecutive ones.
241                 // Here we simply escape the double quote and let our
242                 // simple parser in Systemcall.cpp do the substitution.
243                 return '"' + subst(name, "\"", "\\\"") + '"';
244 #endif
245         case quote_shell_filename:
246                 return quoteName(os::external_path(name), quote_shell);
247         case quote_python:
248                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
249                      + "\"";
250         }
251         // shut up stupid compiler
252         return string();
253 }
254
255
256 #if 0
257 // Uses a string of paths separated by ";"s to find a file to open.
258 // Can't cope with pathnames with a ';' in them. Returns full path to file.
259 // If path entry begins with $$LyX/, use system_lyxdir
260 // If path entry begins with $$User/, use user_lyxdir
261 // Example: "$$User/doc;$$LyX/doc"
262 FileName const fileOpenSearch(string const & path, string const & name,
263                              string const & ext)
264 {
265         FileName real_file;
266         string path_element;
267         bool notfound = true;
268         string tmppath = split(path, path_element, ';');
269
270         while (notfound && !path_element.empty()) {
271                 path_element = os::internal_path(path_element);
272                 if (!suffixIs(path_element, '/'))
273                         path_element += '/';
274                 path_element = subst(path_element, "$$LyX",
275                                      package().system_support().absFileName());
276                 path_element = subst(path_element, "$$User",
277                                      package().user_support().absFileName());
278
279                 real_file = fileSearch(path_element, name, ext);
280
281                 if (real_file.empty()) {
282                         do {
283                                 tmppath = split(tmppath, path_element, ';');
284                         } while (!tmppath.empty() && path_element.empty());
285                 } else {
286                         notfound = false;
287                 }
288         }
289         return real_file;
290 }
291 #endif
292
293
294 // Returns the real name of file name in directory path, with optional
295 // extension ext.
296 FileName const fileSearch(string const & path, string const & name,
297                           string const & exts, search_mode mode)
298 {
299         // if `name' is an absolute path, we ignore the setting of `path'
300         // Expand Environmentvariables in 'name'
301         string const tmpname = replaceEnvironmentPath(name);
302         FileName fullname(makeAbsPath(tmpname, path));
303         // search first without extension, then with it.
304         if (fullname.isReadableFile())
305                 return fullname;
306         if (exts.empty())
307                 // We are done.
308                 return mode == may_not_exist ? fullname : FileName();
309         int n = 0;
310         string ext = token(exts, ',', n);
311         while (!ext.empty()) {
312                 // Only add the extension if it is not already the extension of
313                 // fullname.
314                 bool addext = getExtension(fullname.absFileName()) != ext;
315                 if (addext) {
316                         if (mode == check_hidpi) {
317                                 FileName fullname2x = FileName(addExtension(fullname.absFileName() + "@2x", ext));
318                                 if (fullname2x.isReadableFile())
319                                         return fullname2x;
320                         }
321                         fullname = FileName(addExtension(fullname.absFileName(), ext));
322                 }
323                 if (fullname.isReadableFile() || mode == may_not_exist)
324                         return fullname;
325                 if (addext)
326                         fullname.changeExtension("");
327                 ext = token(exts, ',', ++n);
328         }
329         return FileName();
330 }
331
332
333 // Search the file name.ext in the subdirectory dir of
334 //   1) user_lyxdir
335 //   2) build_lyxdir (if not empty)
336 //   3) system_lyxdir
337 FileName const libFileSearch(string const & dir, string const & name,
338                            string const & ext, search_mode mode)
339 {
340         FileName fullname = fileSearch(addPath(package().user_support().absFileName(), dir),
341                                      name, ext, mode);
342         if (!fullname.empty())
343                 return fullname;
344
345         if (!package().build_support().empty())
346                 fullname = fileSearch(addPath(package().build_support().absFileName(), dir),
347                                       name, ext, mode);
348         if (!fullname.empty())
349                 return fullname;
350
351         return fileSearch(addPath(package().system_support().absFileName(), dir),
352                                       name, ext, mode);
353 }
354
355
356 FileName const i18nLibFileSearch(string const & dir, string const & name,
357                   string const & ext)
358 {
359         /* The highest priority value is the `LANGUAGE' environment
360            variable. But we don't use the value if the currently
361            selected locale is the C locale. This is a GNU extension.
362
363            Otherwise, w use a trick to guess what support/gettext.has done:
364            each po file is able to tell us its name. (JMarc)
365         */
366
367         string lang = getGuiMessages().language();
368         string const language = getEnv("LANGUAGE");
369         if (!lang.empty() && !language.empty())
370                 lang = language;
371
372         string l;
373         lang = split(lang, l, ':');
374         while (!l.empty()) {
375                 FileName tmp;
376                 // First try with the full name
377                 tmp = libFileSearch(addPath(dir, l), name, ext);
378                 if (!tmp.empty())
379                         return tmp;
380
381                 // Then the name without country code
382                 string const shortl = token(l, '_', 0);
383                 if (shortl != l) {
384                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
385                         if (!tmp.empty())
386                                 return tmp;
387                 }
388
389 #if 1
390                 // For compatibility, to be removed later (JMarc)
391                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
392                                     ext);
393                 if (!tmp.empty()) {
394                         lyxerr << "i18nLibFileSearch: File `" << tmp
395                                << "' has been found by the old method" <<endl;
396                         return tmp;
397                 }
398 #endif
399                 lang = split(lang, l, ':');
400         }
401
402         return libFileSearch(dir, name, ext);
403 }
404
405
406 FileName const imageLibFileSearch(string & dir, string const & name,
407                   string const & ext, search_mode mode)
408 {
409         if (!lyx::lyxrc.icon_set.empty()) {
410                 string const imagedir = addPath(dir, lyx::lyxrc.icon_set);
411                 FileName const fn = libFileSearch(imagedir, name, ext, mode);
412                 if (fn.exists()) {
413                         dir = imagedir;
414                         return fn;
415                 }
416         }
417         return libFileSearch(dir, name, ext, mode);
418 }
419
420
421 string const commandPrep(string const & command_in)
422 {
423         static string const token_scriptpath = "$$s/";
424         string const python_call = "python -tt";
425
426         string command = command_in;
427         if (prefixIs(command_in, python_call))
428                 command = os::python() + command_in.substr(python_call.length());
429
430         // Find the starting position of "$$s/"
431         string::size_type const pos1 = command.find(token_scriptpath);
432         if (pos1 == string::npos)
433                 return command;
434         // Find the end of the "$$s/some_subdir/some_script" word within
435         // command. Assumes that the script name does not contain spaces.
436         string::size_type const start_script = pos1 + 4;
437         string::size_type const pos2 = command.find(' ', start_script);
438         string::size_type const size_script = pos2 == string::npos?
439                 (command.size() - start_script) : pos2 - start_script;
440
441         // Does this script file exist?
442         string const script =
443                 libFileSearch(".", command.substr(start_script, size_script)).absFileName();
444
445         if (script.empty()) {
446                 // Replace "$$s/" with ""
447                 command.erase(pos1, 4);
448         } else {
449                 quote_style style = quote_shell;
450                 if (prefixIs(command, os::python()))
451                         style = quote_python;
452
453                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
454                 string::size_type const size_replace = size_script + 4;
455                 command.replace(pos1, size_replace, quoteName(script, style));
456         }
457
458         return command;
459 }
460
461
462 FileName const tempFileName(string const & mask, bool const dir)
463 {
464         FileName tempfile = TempFile(mask).name();
465         // Since the QTemporaryFile object is destroyed at function return
466         // (which is what is intended here), the next call to this function
467         // may return the same file name again.
468         // Thus, in order to prevent race conditions, we track returned names
469         // and create our own unique names if QTemporaryFile returns a name again.
470         if (tmp_names_.find(tempfile.absFileName()) == tmp_names_.end()) {
471                 tmp_names_.insert(tempfile.absFileName());
472                 return tempfile;
473         }
474
475         // OK, we need another name. Simply append digits.
476         FileName tmp = tempfile;
477         string ext;
478         if (!dir) {
479                 // Store and remove extensions
480                 ext = "." + tempfile.extension();
481                 tmp.changeExtension("");
482         }
483         for (int i = 1; i < INT_MAX ;++i) {
484                 // Append digit to filename and re-add extension
485                 string const new_fn =
486                         tmp.absFileName() + convert<string>(i) + ext;
487                 if (tmp_names_.find(new_fn) == tmp_names_.end()) {
488                         tmp_names_.insert(new_fn);
489                         tempfile.set(new_fn);
490                         return tempfile;
491                 }
492         }
493
494         // This should not happen!
495         LYXERR0("tempFileName(): Could not create unique temp file name!");
496         return tempfile;
497 }
498
499
500 void removeTempFile(FileName const & fn)
501 {
502         if (!fn.exists())
503                 return;
504
505         string const abs = fn.absFileName();
506         if (tmp_names_.find(abs) != tmp_names_.end())
507                 tmp_names_.erase(abs);
508         fn.removeFile();
509 }
510
511
512 static FileName createTmpDir(FileName const & tempdir, string const & mask)
513 {
514         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
515                 << "createTmpDir:    mask=`" << mask << '\'');
516
517         QFileInfo tmp_fi(QDir(toqstr(tempdir.absFileName())), toqstr(mask));
518         FileName const tmpfl =
519                 tempFileName(fromqstr(tmp_fi.absoluteFilePath()) + ".XXXXXXXXXXXX", true);
520
521         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
522                 LYXERR0("LyX could not create temporary directory in " << tempdir
523                         << "'");
524                 return FileName();
525         }
526
527         return tmpfl;
528 }
529
530
531 FileName const createLyXTmpDir(FileName const & deflt)
532 {
533         if (deflt.empty() || deflt == package().system_temp_dir())
534                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
535
536         if (deflt.createDirectory(0777))
537                 return deflt;
538
539         if (deflt.isDirWritable()) {
540                 // deflt could not be created because it
541                 // did exist already, so let's create our own
542                 // dir inside deflt.
543                 return createTmpDir(deflt, "lyx_tmpdir");
544         } else {
545                 // some other error occurred.
546                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
547         }
548 }
549
550
551 // Strip filename from path name
552 string const onlyPath(string const & filename)
553 {
554         // If empty filename, return empty
555         if (filename.empty())
556                 return filename;
557
558         // Find last / or start of filename
559         size_t j = filename.rfind('/');
560         return j == string::npos ? "./" : filename.substr(0, j + 1);
561 }
562
563
564 // Convert relative path into absolute path based on a basepath.
565 // If relpath is absolute, just use that.
566 // If basepath is empty, use CWD as base.
567 // Note that basePath can be a relative path, in the sense that it may
568 // not begin with "/" (e.g.), but it should NOT contain such constructs
569 // as "/../".
570 // FIXME It might be nice if the code didn't simply assume that.
571 FileName const makeAbsPath(string const & relPath, string const & basePath)
572 {
573         // checks for already absolute path
574         if (FileName::isAbsolute(relPath))
575                 return FileName(relPath);
576
577         // Copies given paths
578         string tempRel = os::internal_path(relPath);
579         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
580         tempRel = subst(tempRel, "//", "/");
581
582         string tempBase;
583
584         if (FileName::isAbsolute(basePath))
585                 tempBase = basePath;
586         else
587                 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
588
589         // Handle /./ at the end of the path
590         while (suffixIs(tempBase, "/./"))
591                 tempBase.erase(tempBase.length() - 2);
592
593         // processes relative path
594         string rTemp = tempRel;
595         string temp;
596
597         // Check for a leading "~"
598         // Split by first /
599         rTemp = split(rTemp, temp, '/');
600         if (temp == "~") {
601                 tempBase = Package::get_home_dir().absFileName();
602                 tempRel = rTemp;
603         }
604
605         rTemp = tempRel;
606         while (!rTemp.empty()) {
607                 // Split by next /
608                 rTemp = split(rTemp, temp, '/');
609
610                 if (temp == ".") continue;
611                 if (temp == "..") {
612                         // Remove one level of TempBase
613                         if (tempBase.length() <= 1) {
614                                 //this is supposed to be an absolute path, so...
615                                 tempBase = "/";
616                                 continue;
617                         }
618                         //erase a trailing slash if there is one
619                         if (suffixIs(tempBase, "/"))
620                                 tempBase.erase(tempBase.length() - 1, string::npos);
621
622                         string::size_type i = tempBase.length() - 1;
623                         while (i > 0 && tempBase[i] != '/')
624                                 --i;
625                         if (i > 0)
626                                 tempBase.erase(i, string::npos);
627                         else
628                                 tempBase = '/';
629                 } else if (temp.empty() && !rTemp.empty()) {
630                                 tempBase = os::current_root() + rTemp;
631                                 rTemp.erase();
632                 } else {
633                         // Add this piece to TempBase
634                         if (!suffixIs(tempBase, '/'))
635                                 tempBase += '/';
636                         tempBase += temp;
637                 }
638         }
639
640         // returns absolute path
641         return FileName(tempBase);
642 }
643
644
645 // Correctly append filename to the pathname.
646 // If pathname is '.', then don't use pathname.
647 // Chops any path of filename.
648 string const addName(string const & path, string const & fname)
649 {
650         string const basename = onlyFileName(fname);
651         string buf;
652
653         if (path != "." && path != "./" && !path.empty()) {
654                 buf = os::internal_path(path);
655                 if (!suffixIs(path, '/'))
656                         buf += '/';
657         }
658
659         return buf + basename;
660 }
661
662
663 // Strips path from filename
664 string const onlyFileName(string const & fname)
665 {
666         if (fname.empty())
667                 return fname;
668
669         string::size_type j = fname.rfind('/');
670         if (j == string::npos) // no '/' in fname
671                 return fname;
672
673         // Strip to basename
674         return fname.substr(j + 1);
675 }
676
677
678 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
679 string const replaceEnvironmentPath(string const & path)
680 {
681         // ${VAR} is defined as
682         // $\{[A-Za-z_][A-Za-z_0-9]*\}
683         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
684
685         // $VAR is defined as:
686         // $[A-Za-z_][A-Za-z_0-9]*
687         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
688
689         // Coverity thinks that the regex constructor can return an
690         // exception. We know that it is not true since our regex are
691         // hardcoded, but we have to protect against that nevertheless.
692         try {
693                 static regex const envvar_br_re("(.*)" + envvar_br + "(.*)");
694                 static regex const envvar_re("(.*)" + envvar + "(.*)");
695                 string result = path;
696                 while (1) {
697                         smatch what;
698                         if (!regex_match(result, what, envvar_br_re)) {
699                                 if (!regex_match(result, what, envvar_re))
700                                         break;
701                         }
702                         string env_var = getEnv(what.str(2));
703                         result = what.str(1) + env_var + what.str(3);
704                 }
705                 return result;
706         } catch (exception const & e) {
707                 LYXERR0("Something is very wrong: " << e.what());
708                 return path;
709         }
710 }
711
712
713 // Return a command prefix for setting the environment of the TeX engine.
714 string latexEnvCmdPrefix(string const & path, string const & lpath)
715 {
716         bool use_lpath = !(lpath.empty() || lpath == "." || lpath == "./");
717
718         if (path.empty() || (lyxrc.texinputs_prefix.empty() && !use_lpath))
719                 return string();
720
721         string texinputs_prefix = lyxrc.texinputs_prefix.empty() ? string()
722                 : os::latex_path_list(
723                         replaceCurdirPath(path, lyxrc.texinputs_prefix));
724         string const allother_prefix = os::latex_path_list(path);
725         string const sep = string(1, os::path_separator(os::TEXENGINE));
726         string const texinputs = getEnv("TEXINPUTS");
727         string const bibinputs = getEnv("BIBINPUTS");
728         string const bstinputs = getEnv("BSTINPUTS");
729         string const texfonts = getEnv("TEXFONTS");
730
731         if (use_lpath) {
732                 string const abslpath = FileName::isAbsolute(lpath)
733                         ? os::latex_path(lpath)
734                         : os::latex_path(FileName(path + "/" + lpath).realPath());
735                 if (texinputs_prefix.empty())
736                         texinputs_prefix = abslpath;
737                 else if (suffixIs(texinputs_prefix, sep))
738                         texinputs_prefix.append(abslpath + sep);
739                 else
740                         texinputs_prefix.append(sep + abslpath);
741         }
742
743         if (os::shell() == os::UNIX)
744                 return "env TEXINPUTS=\"." + sep + texinputs_prefix
745                                            + sep + texinputs + "\" "
746                          + "BIBINPUTS=\"." + sep + allother_prefix
747                                            + sep + bibinputs + "\" "
748                          + "BSTINPUTS=\"." + sep + allother_prefix
749                                            + sep + bstinputs + "\" "
750                          + "TEXFONTS=\"."  + sep + allother_prefix
751                                            + sep + texfonts + "\" ";
752         else
753                 // NOTE: the dummy blank dirs are necessary to force the
754                 //       QProcess parser to quote the argument (see bug 9453)
755                 return "cmd /d /c set \"TEXINPUTS=." + sep + " "
756                                                 + sep + texinputs_prefix
757                                                 + sep + texinputs + "\" & "
758                                + "set \"BIBINPUTS=." + sep + " "
759                                                 + sep + allother_prefix
760                                                 + sep + bibinputs + "\" & "
761                                + "set \"BSTINPUTS=." + sep + " "
762                                                 + sep + allother_prefix
763                                                 + sep + bstinputs + "\" & "
764                                + "set \"TEXFONTS=."  + sep + " "
765                                                 + sep + allother_prefix
766                                                 + sep + texfonts + "\" & ";
767 }
768
769
770 // Replace current directory in all elements of a path list with a given path.
771 string const replaceCurdirPath(string const & path, string const & pathlist)
772 {
773         string const oldpathlist = replaceEnvironmentPath(pathlist);
774         char const sep = os::path_separator();
775         string newpathlist;
776
777         for (size_t i = 0, k = 0; i != string::npos; k = i) {
778                 i = oldpathlist.find(sep, i);
779                 string p = oldpathlist.substr(k, i - k);
780                 if (FileName::isAbsolute(p)) {
781                         newpathlist += p;
782                 } else if (i > k) {
783                         size_t offset = 0;
784                         if (p == ".") {
785                                 offset = 1;
786                         } else if (prefixIs(p, "./")) {
787                                 offset = 2;
788                                 while (p[offset] == '/')
789                                         ++offset;
790                         }
791                         newpathlist += addPath(path, p.substr(offset));
792                         if (suffixIs(p, "//"))
793                                 newpathlist += '/';
794                 }
795                 if (i != string::npos) {
796                         newpathlist += sep;
797                         // Stop here if the last element is empty
798                         if (++i == oldpathlist.length())
799                                 break;
800                 }
801         }
802         return newpathlist;
803 }
804
805
806 // Make relative path out of two absolute paths
807 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
808 // Makes relative path out of absolute path. If it is deeper than basepath,
809 // it's easy. If basepath and abspath share something (they are all deeper
810 // than some directory), it'll be rendered using ..'s. If they are completely
811 // different, then the absolute path will be used as relative path.
812 {
813         docstring::size_type const abslen = abspath.length();
814         docstring::size_type const baselen = basepath.length();
815
816         docstring::size_type i = os::common_path(abspath, basepath);
817
818         if (i == 0) {
819                 // actually no match - cannot make it relative
820                 return abspath;
821         }
822
823         // Count how many dirs there are in basepath above match
824         // and append as many '..''s into relpath
825         docstring buf;
826         docstring::size_type j = i;
827         while (j < baselen) {
828                 if (basepath[j] == '/') {
829                         if (j + 1 == baselen)
830                                 break;
831                         buf += "../";
832                 }
833                 ++j;
834         }
835
836         // Append relative stuff from common directory to abspath
837         if (abspath[i] == '/')
838                 ++i;
839         for (; i < abslen; ++i)
840                 buf += abspath[i];
841         // Remove trailing /
842         if (suffixIs(buf, '/'))
843                 buf.erase(buf.length() - 1);
844         // Substitute empty with .
845         if (buf.empty())
846                 buf = '.';
847         return buf;
848 }
849
850
851 // Append sub-directory(ies) to a path in an intelligent way
852 string const addPath(string const & path, string const & path_2)
853 {
854         string buf;
855         string const path2 = os::internal_path(path_2);
856
857         if (!path.empty() && path != "." && path != "./") {
858                 buf = os::internal_path(path);
859                 if (path[path.length() - 1] != '/')
860                         buf += '/';
861         }
862
863         if (!path2.empty()) {
864                 string::size_type const p2start = path2.find_first_not_of('/');
865                 string::size_type const p2end = path2.find_last_not_of('/');
866                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
867                 buf += tmp + '/';
868         }
869         return buf;
870 }
871
872
873 string const changeExtension(string const & oldname, string const & extension)
874 {
875         string::size_type const last_slash = oldname.rfind('/');
876         string::size_type last_dot = oldname.rfind('.');
877         if (last_dot < last_slash && last_slash != string::npos)
878                 last_dot = string::npos;
879
880         string ext;
881         // Make sure the extension starts with a dot
882         if (!extension.empty() && extension[0] != '.')
883                 ext= '.' + extension;
884         else
885                 ext = extension;
886
887         return os::internal_path(oldname.substr(0, last_dot) + ext);
888 }
889
890
891 string const removeExtension(string const & name)
892 {
893         return changeExtension(name, string());
894 }
895
896
897 string const addExtension(string const & name, string const & extension)
898 {
899         if (!extension.empty() && extension[0] != '.')
900                 return name + '.' + extension;
901         return name + extension;
902 }
903
904
905 /// Return the extension of the file (not including the .)
906 string const getExtension(string const & name)
907 {
908         string::size_type const last_slash = name.rfind('/');
909         string::size_type const last_dot = name.rfind('.');
910         if (last_dot != string::npos &&
911             (last_slash == string::npos || last_dot > last_slash))
912                 return name.substr(last_dot + 1,
913                                    name.length() - (last_dot + 1));
914         else
915                 return string();
916 }
917
918
919 string const unzippedFileName(string const & zipped_file)
920 {
921         string const ext = getExtension(zipped_file);
922         if (ext == "gz" || ext == "z" || ext == "Z")
923                 return changeExtension(zipped_file, string());
924         else if (ext == "svgz")
925                 return changeExtension(zipped_file, "svg");
926         return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
927 }
928
929
930 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
931 {
932         FileName const tempfile = FileName(unzipped_file.empty() ?
933                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
934                 unzipped_file);
935         // Run gunzip
936         string const command = "gunzip -c " +
937                 zipped_file.toFilesystemEncoding() + " > " +
938                 tempfile.toFilesystemEncoding();
939         Systemcall one;
940         one.startscript(Systemcall::Wait, command);
941         // test that command was executed successfully (anon)
942         // yes, please do. (Lgb)
943         return tempfile;
944 }
945
946
947 docstring const makeDisplayPath(string const & path, unsigned int threshold)
948 {
949         string str = path;
950
951         // If file is from LyXDir, display it as if it were relative.
952         string const system = package().system_support().absFileName();
953         if (prefixIs(str, system) && str != system)
954                 return from_utf8("[" + str.erase(0, system.length()) + "]");
955
956         // replace /home/blah with ~/
957         string const home = Package::get_home_dir().absFileName();
958         if (!home.empty() && prefixIs(str, home))
959                 str = subst(str, home, "~");
960
961         if (str.length() <= threshold)
962                 return from_utf8(os::external_path(str));
963
964         string const prefix = ".../";
965         docstring dstr = from_utf8(str);
966         docstring temp;
967
968         while (dstr.length() > threshold)
969                 dstr = split(dstr, temp, '/');
970
971         // Did we shorten everything away?
972         if (dstr.empty()) {
973                 // Yes, filename itself is too long.
974                 // Pick the start and the end of the filename.
975                 docstring fstr = from_utf8(onlyFileName(path));
976                 dstr = fstr;
977                 if (support::truncateWithEllipsis(dstr, threshold / 2))
978                         dstr += fstr.substr(fstr.length() - threshold / 2 - 2,
979                                                                 docstring::npos);
980         }
981
982         return from_utf8(os::external_path(prefix + to_utf8(dstr)));
983 }
984
985
986 #ifdef HAVE_READLINK
987 bool readLink(FileName const & file, FileName & link)
988 {
989         string const encoded = file.toFilesystemEncoding();
990 #ifdef HAVE_DEF_PATH_MAX
991         char linkbuffer[PATH_MAX + 1];
992         ssize_t const nRead = ::readlink(encoded.c_str(),
993                                      linkbuffer, sizeof(linkbuffer) - 1);
994         if (nRead <= 0)
995                 return false;
996         linkbuffer[nRead] = '\0'; // terminator
997 #else
998         vector<char> buf(1024);
999         int nRead = -1;
1000
1001         while (true) {
1002                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
1003                 if (nRead < 0) {
1004                         return false;
1005                 }
1006                 if (static_cast<size_t>(nRead) < buf.size() - 1) {
1007                         break;
1008                 }
1009                 buf.resize(buf.size() * 2);
1010         }
1011         buf[nRead] = '\0'; // terminator
1012         const char * linkbuffer = &buf[0];
1013 #endif
1014         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
1015         return true;
1016 }
1017 #else
1018 bool readLink(FileName const &, FileName &)
1019 {
1020         return false;
1021 }
1022 #endif
1023
1024
1025 cmd_ret const runCommand(string const & cmd)
1026 {
1027         // FIXME: replace all calls to RunCommand with ForkedCall
1028         // (if the output is not needed) or the code in ISpell.cpp
1029         // (if the output is needed).
1030
1031         // One question is if we should use popen or
1032         // create our own popen based on fork, exec, pipe
1033         // of course the best would be to have a
1034         // pstream (process stream), with the
1035         // variants ipstream, opstream
1036
1037         if (verbose)
1038                 lyxerr << "\nRunning: " << cmd << endl;
1039         else
1040                 LYXERR(Debug::INFO,"Running: " << cmd);
1041
1042 #if defined (_WIN32)
1043         STARTUPINFO startup;
1044         PROCESS_INFORMATION process;
1045         SECURITY_ATTRIBUTES security;
1046         HANDLE in, out;
1047         FILE * inf = 0;
1048         bool err2out = false;
1049         string command;
1050         string const infile = trim(split(cmd, command, '<'), " \"");
1051         command = rtrim(command);
1052         if (suffixIs(command, "2>&1")) {
1053                 command = rtrim(command, "2>&1");
1054                 err2out = true;
1055         }
1056         string const cmdarg = "/d /c " + command;
1057         string const comspec = getEnv("COMSPEC");
1058
1059         security.nLength = sizeof(SECURITY_ATTRIBUTES);
1060         security.bInheritHandle = TRUE;
1061         security.lpSecurityDescriptor = NULL;
1062
1063         if (CreatePipe(&in, &out, &security, 0)) {
1064                 memset(&startup, 0, sizeof(STARTUPINFO));
1065                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
1066
1067                 startup.cb = sizeof(STARTUPINFO);
1068                 startup.dwFlags = STARTF_USESTDHANDLES;
1069
1070                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
1071                 startup.hStdInput = infile.empty()
1072                         ? GetStdHandle(STD_INPUT_HANDLE)
1073                         : CreateFile(infile.c_str(), GENERIC_READ,
1074                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
1075                                 FILE_ATTRIBUTE_NORMAL, NULL);
1076                 startup.hStdOutput = out;
1077
1078                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
1079                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
1080                                 &security, &security, TRUE, CREATE_NO_WINDOW,
1081                                 0, 0, &startup, &process)) {
1082
1083                         CloseHandle(process.hThread);
1084                         int fno = _open_osfhandle((intptr_t)in, _O_RDONLY);
1085                         CloseHandle(out);
1086                         inf = _fdopen(fno, "r");
1087                 }
1088         }
1089 #elif defined (HAVE_POPEN)
1090         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1091 #elif defined (HAVE__POPEN)
1092         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1093 #else
1094 #error No popen() function.
1095 #endif
1096
1097         // (Claus Hentschel) Check if popen was successful ;-)
1098         if (!inf) {
1099                 lyxerr << "RunCommand:: could not start child process" << endl;
1100                 return make_pair(-1, string());
1101         }
1102
1103         string ret;
1104         int c = fgetc(inf);
1105         while (c != EOF) {
1106                 ret += static_cast<char>(c);
1107                 c = fgetc(inf);
1108         }
1109
1110 #if defined (_WIN32)
1111         WaitForSingleObject(process.hProcess, INFINITE);
1112         DWORD pret;
1113         if (!GetExitCodeProcess(process.hProcess, &pret))
1114                 pret = -1;
1115         if (!infile.empty())
1116                 CloseHandle(startup.hStdInput);
1117         CloseHandle(process.hProcess);
1118         if (fclose(inf) != 0)
1119                 pret = -1;
1120 #elif defined (HAVE_PCLOSE)
1121         int const pret = pclose(inf);
1122 #elif defined (HAVE__PCLOSE)
1123         int const pret = _pclose(inf);
1124 #else
1125 #error No pclose() function.
1126 #endif
1127
1128         if (pret == -1)
1129                 perror("RunCommand:: could not terminate child process");
1130
1131         return make_pair(pret, ret);
1132 }
1133
1134
1135 FileName const findtexfile(string const & fil, string const & /*format*/,
1136                                                    bool const onlykpse)
1137 {
1138         /* There is no problem to extend this function too use other
1139            methods to look for files. It could be setup to look
1140            in environment paths and also if wanted as a last resort
1141            to a recursive find. One of the easier extensions would
1142            perhaps be to use the LyX file lookup methods. But! I am
1143            going to implement this until I see some demand for it.
1144            Lgb
1145         */
1146
1147         // If the file can be found directly, we just return a
1148         // absolute path version of it.
1149         if (!onlykpse) {
1150                 FileName const absfile(makeAbsPath(fil));
1151                 if (absfile.exists())
1152                         return absfile;
1153         }
1154
1155         // Now we try to find it using kpsewhich.
1156         // It seems from the kpsewhich manual page that it is safe to use
1157         // kpsewhich without --format: "When the --format option is not
1158         // given, the search path used when looking for a file is inferred
1159         // from the name given, by looking for a known extension. If no
1160         // known extension is found, the search path for TeX source files
1161         // is used."
1162         // However, we want to take advantage of the format sine almost all
1163         // the different formats has environment variables that can be used
1164         // to controll which paths to search. f.ex. bib looks in
1165         // BIBINPUTS and TEXBIB. Small list follows:
1166         // bib - BIBINPUTS, TEXBIB
1167         // bst - BSTINPUTS
1168         // graphic/figure - TEXPICTS, TEXINPUTS
1169         // ist - TEXINDEXSTYLE, INDEXSTYLE
1170         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1171         // tex - TEXINPUTS
1172         // tfm - TFMFONTS, TEXFONTS
1173         // This means that to use kpsewhich in the best possible way we
1174         // should help it by setting additional path in the approp. envir.var.
1175         string const kpsecmd = "kpsewhich " + fil;
1176
1177         cmd_ret const c = runCommand(kpsecmd);
1178
1179         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
1180                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
1181         if (c.first != -1)
1182                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
1183         else
1184                 return FileName();
1185 }
1186
1187
1188 int compare_timestamps(FileName const & file1, FileName const & file2)
1189 {
1190         // If the original is newer than the copy, then copy the original
1191         // to the new directory.
1192
1193         int cmp = 0;
1194         if (file1.exists() && file2.exists()) {
1195                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1196                 if (tmp != 0)
1197                         cmp = tmp > 0 ? 1 : -1;
1198
1199         } else if (file1.exists()) {
1200                 cmp = 1;
1201         } else if (file2.exists()) {
1202                 cmp = -1;
1203         }
1204
1205         return cmp;
1206 }
1207
1208
1209 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1210 {
1211         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1212         if (script.empty()) {
1213                 LYXERR0("Could not find bind file conversion "
1214                                 "script prefs2prefs.py.");
1215                 return false;
1216         }
1217
1218         ostringstream command;
1219         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1220           << ' ' << (lfuns ? "-l" : "-p") << ' '
1221                 << quoteName(filename.toFilesystemEncoding())
1222                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1223         string const command_str = command.str();
1224
1225         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1226
1227         cmd_ret const ret = runCommand(command_str);
1228         if (ret.first != 0) {
1229                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1230                 return false;
1231         }
1232         return true;
1233 }
1234
1235
1236 bool configFileNeedsUpdate(string const & file)
1237 {
1238         // We cannot initialize configure_script directly because the package
1239         // is not initialized yet when static objects are constructed.
1240         static FileName configure_script;
1241         static bool firstrun = true;
1242         if (firstrun) {
1243                 configure_script =
1244                         FileName(addName(package().system_support().absFileName(),
1245                                 "configure.py"));
1246                 firstrun = false;
1247         }
1248
1249         FileName absfile =
1250                 FileName(addName(package().user_support().absFileName(), file));
1251         return !absfile.exists()
1252                 || configure_script.lastModified() > absfile.lastModified();
1253 }
1254
1255
1256 int fileLock(const char * lock_file)
1257 {
1258         int fd = -1;
1259 #if defined(HAVE_LOCKF)
1260         fd = open(lock_file, O_CREAT|O_APPEND|O_SYNC|O_RDWR, 0666);
1261         if (fd == -1)
1262                 return -1;
1263         if (lockf(fd, F_LOCK, 0) != 0) {
1264                 close(fd);
1265                 return -1;
1266         }
1267 #endif
1268         return fd;
1269 }
1270
1271
1272 void fileUnlock(int fd, const char * /* lock_file*/)
1273 {
1274 #if defined(HAVE_LOCKF)
1275         if (fd >= 0) {
1276                 if (lockf(fd, F_ULOCK, 0))
1277                         LYXERR0("Can't unlock the file.");
1278                 close(fd);
1279         }
1280 #endif
1281 }
1282
1283 } //namespace support
1284 } // namespace lyx