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