]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Keep dialog connected to cross-ref inset after Apply.
[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 "support/filetools.h"
25
26 #include "LyX.h"
27 #include "LyXRC.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
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 <regex>
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, nullptr) != 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         // if the LANGUAGE variable is set, use it as a fallback for searching for files.
360         string lang = getGuiMessages().language();
361         string const language = getEnv("LANGUAGE");
362         if (!language.empty())
363                 lang += ":" + language;
364
365         for (auto const & l : getVectorFromString(lang, ":")) {
366                 FileName tmp;
367                 // First try with the full name
368                 // `en' files are not in a subdirectory
369                 if (l == "en")
370                         tmp = libFileSearch(dir, name, ext);
371                 else
372                         tmp = libFileSearch(addPath(dir, l), name, ext);
373                 if (!tmp.empty())
374                         return tmp;
375
376                 // Then the name without country code
377                 string const shortl = token(l, '_', 0);
378                 if (shortl != l) {
379                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
380                         if (!tmp.empty())
381                                 return tmp;
382                 }
383         }
384
385         return libFileSearch(dir, name, ext);
386 }
387
388
389 FileName const imageLibFileSearch(string & dir, string const & name,
390                   string const & ext, search_mode mode)
391 {
392         if (!lyx::lyxrc.icon_set.empty()) {
393                 string const imagedir = addPath(dir, lyx::lyxrc.icon_set);
394                 FileName const fn = libFileSearch(imagedir, name, ext, mode);
395                 if (fn.exists()) {
396                         dir = imagedir;
397                         return fn;
398                 }
399         }
400         return libFileSearch(dir, name, ext, mode);
401 }
402
403
404 string const commandPrep(string const & command_in)
405 {
406         static string const token_scriptpath = "$$s/";
407         string const python_call = "python -tt";
408
409         string command = command_in;
410         if (prefixIs(command_in, python_call))
411                 command = os::python() + command_in.substr(python_call.length());
412
413         // Find the starting position of "$$s/"
414         string::size_type const pos1 = command.find(token_scriptpath);
415         if (pos1 == string::npos)
416                 return command;
417         // Find the end of the "$$s/some_subdir/some_script" word within
418         // command. Assumes that the script name does not contain spaces.
419         string::size_type const start_script = pos1 + 4;
420         string::size_type const pos2 = command.find(' ', start_script);
421         string::size_type const size_script = pos2 == string::npos?
422                 (command.size() - start_script) : pos2 - start_script;
423
424         // Does this script file exist?
425         string const script =
426                 libFileSearch(".", command.substr(start_script, size_script)).absFileName();
427
428         if (script.empty()) {
429                 // Replace "$$s/" with ""
430                 command.erase(pos1, 4);
431         } else {
432                 quote_style style = quote_shell;
433                 if (prefixIs(command, os::python()))
434                         style = quote_python;
435
436                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
437                 string::size_type const size_replace = size_script + 4;
438                 command.replace(pos1, size_replace, quoteName(script, style));
439         }
440
441         return command;
442 }
443
444
445 FileName const tempFileName(FileName const & tempdir, string const & mask, bool const dir)
446 {
447         return tempFileName(TempFile(tempdir, mask).name(), dir);
448 }
449
450
451 FileName const tempFileName(string const & mask, bool const dir)
452 {
453         return tempFileName(TempFile(mask).name(), dir);
454 }
455
456
457 FileName const tempFileName(FileName tempfile, bool const dir)
458 {
459         // Since the QTemporaryFile object is destroyed at function return
460         // (which is what is intended here), the next call to this function
461         // may return the same file name again.
462         // Thus, in order to prevent race conditions, we track returned names
463         // and create our own unique names if QTemporaryFile returns a name again.
464         if (tmp_names_.find(tempfile.absFileName()) == tmp_names_.end()) {
465                 tmp_names_.insert(tempfile.absFileName());
466                 return tempfile;
467         }
468
469         // OK, we need another name. Simply append digits.
470         FileName tmp = tempfile;
471         string ext;
472         if (!dir) {
473                 // Store and remove extensions
474                 ext = "." + tempfile.extension();
475                 tmp.changeExtension("");
476         }
477         for (int i = 1; i < INT_MAX ;++i) {
478                 // Append digit to filename and re-add extension
479                 string const new_fn =
480                         tmp.absFileName() + convert<string>(i) + ext;
481                 if (tmp_names_.find(new_fn) == tmp_names_.end()) {
482                         tmp_names_.insert(new_fn);
483                         tempfile.set(new_fn);
484                         return tempfile;
485                 }
486         }
487
488         // This should not happen!
489         LYXERR0("tempFileName(): Could not create unique temp file name!");
490         return tempfile;
491 }
492
493
494 void removeTempFile(FileName const & fn)
495 {
496         if (!fn.exists())
497                 return;
498
499         string const abs = fn.absFileName();
500         tmp_names_.erase(abs);
501         fn.removeFile();
502 }
503
504
505 static FileName createTmpDir(FileName const & tempdir, string const & mask)
506 {
507         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
508                 << "createTmpDir:    mask=`" << mask << '\'');
509
510         QFileInfo tmp_fi(QDir(toqstr(tempdir.absFileName())), toqstr(mask));
511         FileName const tmpfl =
512                 tempFileName(FileName(fromqstr(tmp_fi.absolutePath())),
513                              fromqstr(tmp_fi.fileName()) + ".XXXXXXXXXXXX", true);
514
515         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
516                 LYXERR0("LyX could not create temporary directory in " << tempdir
517                         << "'");
518                 return FileName();
519         }
520
521         return tmpfl;
522 }
523
524
525 FileName const createLyXTmpDir(FileName const & deflt)
526 {
527         if (deflt.empty() || deflt == package().system_temp_dir())
528                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
529
530         if (deflt.createDirectory(0777))
531                 return deflt;
532
533         if (deflt.isDirWritable()) {
534                 // deflt could not be created because it
535                 // did exist already, so let's create our own
536                 // dir inside deflt.
537                 return createTmpDir(deflt, "lyx_tmpdir");
538         } else {
539                 // some other error occurred.
540                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
541         }
542 }
543
544
545 // Strip filename from path name
546 string const onlyPath(string const & filename)
547 {
548         // If empty filename, return empty
549         if (filename.empty())
550                 return filename;
551
552         // Find last / or start of filename
553         size_t j = filename.rfind('/');
554         return j == string::npos ? "./" : filename.substr(0, j + 1);
555 }
556
557
558 // Convert relative path into absolute path based on a basepath.
559 // If relpath is absolute, just use that.
560 // If basepath is empty, use CWD as base.
561 // Note that basePath can be a relative path, in the sense that it may
562 // not begin with "/" (e.g.), but it should NOT contain such constructs
563 // as "/../".
564 // FIXME It might be nice if the code didn't simply assume that.
565 FileName const makeAbsPath(string const & relPath, string const & basePath)
566 {
567         // checks for already absolute path
568         if (FileName::isAbsolute(relPath))
569                 return FileName(relPath);
570
571         // Copies given paths
572         string tempRel = os::internal_path(relPath);
573         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
574         tempRel = subst(tempRel, "//", "/");
575
576         string tempBase;
577
578         if (FileName::isAbsolute(basePath))
579                 tempBase = basePath;
580         else
581                 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
582
583         // Handle /./ at the end of the path
584         while (suffixIs(tempBase, "/./"))
585                 tempBase.erase(tempBase.length() - 2);
586
587         // processes relative path
588         string rTemp = tempRel;
589         string temp;
590
591         // Check for a leading "~"
592         // Split by first /
593         rTemp = split(rTemp, temp, '/');
594         if (temp == "~") {
595                 tempBase = Package::get_home_dir().absFileName();
596                 tempRel = rTemp;
597         }
598
599         rTemp = tempRel;
600         while (!rTemp.empty()) {
601                 // Split by next /
602                 rTemp = split(rTemp, temp, '/');
603
604                 if (temp == ".") continue;
605                 if (temp == "..") {
606                         // Remove one level of TempBase
607                         if (tempBase.length() <= 1) {
608                                 //this is supposed to be an absolute path, so...
609                                 tempBase = "/";
610                                 continue;
611                         }
612                         //erase a trailing slash if there is one
613                         if (suffixIs(tempBase, "/"))
614                                 tempBase.erase(tempBase.length() - 1, string::npos);
615
616                         string::size_type i = tempBase.length() - 1;
617                         while (i > 0 && tempBase[i] != '/')
618                                 --i;
619                         if (i > 0)
620                                 tempBase.erase(i, string::npos);
621                         else
622                                 tempBase = '/';
623                 } else if (temp.empty() && !rTemp.empty()) {
624                                 tempBase = os::current_root() + rTemp;
625                                 rTemp.erase();
626                 } else {
627                         // Add this piece to TempBase
628                         if (!suffixIs(tempBase, '/'))
629                                 tempBase += '/';
630                         tempBase += temp;
631                 }
632         }
633
634         // returns absolute path
635         return FileName(tempBase);
636 }
637
638
639 // Correctly append filename to the pathname.
640 // If pathname is '.', then don't use pathname.
641 // Chops any path of filename.
642 string const addName(string const & path, string const & fname)
643 {
644         string const basename = onlyFileName(fname);
645         string buf;
646
647         if (path != "." && path != "./" && !path.empty()) {
648                 buf = os::internal_path(path);
649                 if (!suffixIs(buf, '/'))
650                         buf += '/';
651         }
652
653         return buf + basename;
654 }
655
656
657 string const addPathName(std::string const & path, std::string const & fname)
658 {
659         string const pathpart = onlyPath(fname);
660         string const namepart = onlyFileName(fname);
661         string newpath = path;
662         if (!pathpart.empty())
663                 newpath = addPath(newpath, pathpart);
664         if (!namepart.empty())
665                 newpath = addName(newpath, namepart);
666         return newpath;
667 }
668
669
670 // Strips path from filename
671 string const onlyFileName(string const & fname)
672 {
673         if (fname.empty())
674                 return fname;
675
676         string::size_type j = fname.rfind('/');
677         if (j == string::npos) // no '/' in fname
678                 return fname;
679
680         // Strip to basename
681         return fname.substr(j + 1);
682 }
683
684
685 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
686 // If VAR does not exist, ${VAR} and $VAR are left as is in the string.
687 string const replaceEnvironmentPath(string const & path)
688 {
689         if (!contains(path, '$'))
690                 return path;
691
692         // ${VAR} is defined as
693         // $\{[A-Za-z_][A-Za-z_0-9]*\}
694         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
695
696         // $VAR is defined as:
697         // $[A-Za-z_][A-Za-z_0-9]*
698         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
699
700         // Coverity thinks that the regex constructor can return an
701         // exception. We know that it is not true since our regex are
702         // hardcoded, but we have to protect against that nevertheless.
703         try {
704                 static regex const envvar_br_re("(.*)" + envvar_br + "(.*)");
705                 static regex const envvar_re("(.*)" + envvar + "(.*)");
706                 string result = path;
707                 while (1) {
708                         smatch what;
709                         bool brackets = true;
710                         if (!regex_match(result, what, envvar_br_re)) {
711                                 brackets = false;
712                                 if (!regex_match(result, what, envvar_re))
713                                         break;
714                         }
715                         string env_var = getEnv(what.str(2));
716                         if (env_var.empty()) {
717                                 // temporarily use escape (0x1B) in place of $
718                                 if (brackets)
719                                         env_var = "\e{" + what.str(2) + '}';
720                                 else
721                                         env_var = "\e" + what.str(2);
722                         }
723                         result = what.str(1) + env_var + what.str(3);
724                 }
725                 return subst(result, '\e', '$');
726         } catch (exception const & e) {
727                 LYXERR0("Something is very wrong: " << e.what());
728                 return path;
729         }
730 }
731
732
733 // Return a command prefix for setting the environment of the TeX engine.
734 string latexEnvCmdPrefix(string const & path, string const & lpath)
735 {
736         bool use_lpath = !(lpath.empty() || lpath == "." || lpath == "./");
737
738         if (path.empty() || (lyxrc.texinputs_prefix.empty() && !use_lpath))
739                 return string();
740
741         string texinputs_prefix = lyxrc.texinputs_prefix.empty() ? string()
742                 : os::latex_path_list(
743                         replaceCurdirPath(path, lyxrc.texinputs_prefix));
744         string const allother_prefix = os::latex_path_list(path);
745         string const sep = string(1, os::path_separator(os::TEXENGINE));
746         string const texinputs = getEnv("TEXINPUTS");
747         string const bibinputs = getEnv("BIBINPUTS");
748         string const bstinputs = getEnv("BSTINPUTS");
749         string const texfonts = getEnv("TEXFONTS");
750
751         if (use_lpath) {
752                 string const abslpath = FileName::isAbsolute(lpath)
753                         ? os::latex_path(lpath)
754                         : os::latex_path(FileName(path + "/" + lpath).realPath());
755                 if (texinputs_prefix.empty())
756                         texinputs_prefix = abslpath;
757                 else if (suffixIs(texinputs_prefix, sep))
758                         texinputs_prefix.append(abslpath + sep);
759                 else
760                         texinputs_prefix.append(sep + abslpath);
761         }
762
763         if (os::shell() == os::UNIX)
764                 return "env TEXINPUTS=\"." + sep + texinputs_prefix
765                                            + sep + texinputs + "\" "
766                          + "BIBINPUTS=\"." + sep + allother_prefix
767                                            + sep + bibinputs + "\" "
768                          + "BSTINPUTS=\"." + sep + allother_prefix
769                                            + sep + bstinputs + "\" "
770                          + "TEXFONTS=\"."  + sep + allother_prefix
771                                            + sep + texfonts + "\" ";
772         else
773                 // NOTE: the dummy blank dirs are necessary to force the
774                 //       QProcess parser to quote the argument (see bug 9453)
775                 return "cmd /d /c set \"TEXINPUTS=." + sep + " "
776                                                 + sep + texinputs_prefix
777                                                 + sep + texinputs + "\" & "
778                                + "set \"BIBINPUTS=." + sep + " "
779                                                 + sep + allother_prefix
780                                                 + sep + bibinputs + "\" & "
781                                + "set \"BSTINPUTS=." + sep + " "
782                                                 + sep + allother_prefix
783                                                 + sep + bstinputs + "\" & "
784                                + "set \"TEXFONTS=."  + sep + " "
785                                                 + sep + allother_prefix
786                                                 + sep + texfonts + "\" & ";
787 }
788
789
790 // Replace current directory in all elements of a path list with a given path.
791 string const replaceCurdirPath(string const & path, string const & pathlist)
792 {
793         string const oldpathlist = replaceEnvironmentPath(pathlist);
794         char const sep = os::path_separator();
795         string newpathlist;
796
797         for (size_t i = 0, k = 0; i != string::npos; k = i) {
798                 i = oldpathlist.find(sep, i);
799                 string p = oldpathlist.substr(k, i - k);
800                 if (FileName::isAbsolute(p)) {
801                         newpathlist += p;
802                 } else if (i > k) {
803                         size_t offset = 0;
804                         if (p == ".") {
805                                 offset = 1;
806                         } else if (prefixIs(p, "./")) {
807                                 offset = 2;
808                                 while (p[offset] == '/')
809                                         ++offset;
810                         }
811                         newpathlist += addPath(path, p.substr(offset));
812                         if (suffixIs(p, "//"))
813                                 newpathlist += '/';
814                 }
815                 if (i != string::npos) {
816                         newpathlist += sep;
817                         // Stop here if the last element is empty
818                         if (++i == oldpathlist.length())
819                                 break;
820                 }
821         }
822         return newpathlist;
823 }
824
825
826 // Make relative path out of two absolute paths
827 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
828 // Makes relative path out of absolute path. If it is deeper than basepath,
829 // it's easy. If basepath and abspath share something (they are all deeper
830 // than some directory), it'll be rendered using ..'s. If they are completely
831 // different, then the absolute path will be used as relative path.
832 {
833         docstring::size_type const abslen = abspath.length();
834         docstring::size_type const baselen = basepath.length();
835
836         docstring::size_type i = os::common_path(abspath, basepath);
837
838         if (i == 0) {
839                 // actually no match - cannot make it relative
840                 return abspath;
841         }
842
843         // Count how many dirs there are in basepath above match
844         // and append as many '..''s into relpath
845         docstring buf;
846         docstring::size_type j = i;
847         while (j < baselen) {
848                 if (basepath[j] == '/') {
849                         if (j + 1 == baselen)
850                                 break;
851                         buf += "../";
852                 }
853                 ++j;
854         }
855
856         // Append relative stuff from common directory to abspath
857         if (abspath[i] == '/')
858                 ++i;
859         for (; i < abslen; ++i)
860                 buf += abspath[i];
861         // Remove trailing /
862         if (suffixIs(buf, '/'))
863                 buf.erase(buf.length() - 1);
864         // Substitute empty with .
865         if (buf.empty())
866                 buf = '.';
867         return buf;
868 }
869
870
871 // Append sub-directory(ies) to a path in an intelligent way
872 string const addPath(string const & path, string const & path_2)
873 {
874         string buf;
875         string const path2 = os::internal_path(path_2);
876
877         if (!path.empty() && path != "." && path != "./") {
878                 buf = os::internal_path(path);
879                 if (path[path.length() - 1] != '/')
880                         buf += '/';
881         }
882
883         if (!path2.empty()) {
884                 string::size_type const p2start = path2.find_first_not_of('/');
885                 string::size_type const p2end = path2.find_last_not_of('/');
886                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
887                 buf += tmp + '/';
888         }
889         return buf;
890 }
891
892
893 string const changeExtension(string const & oldname, string const & extension)
894 {
895         string::size_type const last_slash = oldname.rfind('/');
896         string::size_type last_dot = oldname.rfind('.');
897         if (last_dot < last_slash && last_slash != string::npos)
898                 last_dot = string::npos;
899
900         string ext;
901         // Make sure the extension starts with a dot
902         if (!extension.empty() && extension[0] != '.')
903                 ext= '.' + extension;
904         else
905                 ext = extension;
906
907         return os::internal_path(oldname.substr(0, last_dot) + ext);
908 }
909
910
911 string const removeExtension(string const & name)
912 {
913         return changeExtension(name, string());
914 }
915
916
917 string const addExtension(string const & name, string const & extension)
918 {
919         if (!extension.empty() && extension[0] != '.')
920                 return name + '.' + extension;
921         return name + extension;
922 }
923
924
925 /// Return the extension of the file (not including the .)
926 string const getExtension(string const & name)
927 {
928         string::size_type const last_slash = name.rfind('/');
929         string::size_type const last_dot = name.rfind('.');
930         if (last_dot != string::npos &&
931             (last_slash == string::npos || last_dot > last_slash))
932                 return name.substr(last_dot + 1,
933                                    name.length() - (last_dot + 1));
934         else
935                 return string();
936 }
937
938
939 string const unzippedFileName(string const & zipped_file)
940 {
941         string const ext = getExtension(zipped_file);
942         if (ext == "gz" || ext == "z" || ext == "Z")
943                 return changeExtension(zipped_file, string());
944         else if (ext == "svgz")
945                 return changeExtension(zipped_file, "svg");
946         return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
947 }
948
949
950 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
951 {
952         FileName const tempfile = FileName(unzipped_file.empty() ?
953                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
954                 unzipped_file);
955         // Run gunzip
956         string const command = "gunzip -c \"" +
957                 zipped_file.toFilesystemEncoding() + "\" > \"" +
958                 tempfile.toFilesystemEncoding() + "\"";
959         Systemcall one;
960         one.startscript(Systemcall::Wait, command);
961         // test that command was executed successfully (anon)
962         // yes, please do. (Lgb)
963         return tempfile;
964 }
965
966
967 docstring const makeDisplayPath(string const & path, unsigned int threshold)
968 {
969         string str = path;
970
971         // Recode URL encoded chars.
972         str = from_percent_encoding(str);
973
974         // If file is from LyXDir, display it as if it were relative.
975         string const system = package().system_support().absFileName();
976         if (prefixIs(str, system) && str != system)
977                 return from_utf8("[" + str.erase(0, system.length()) + "]");
978
979         // replace /home/blah with ~/
980         string const home = Package::get_home_dir().absFileName();
981         if (!home.empty() && prefixIs(str, home))
982                 str = subst(str, home, "~");
983
984         if (str.length() <= threshold)
985                 return from_utf8(os::external_path(str));
986
987         string const prefix = ".../";
988         docstring dstr = from_utf8(str);
989         docstring temp;
990
991         while (dstr.length() > threshold)
992                 dstr = split(dstr, temp, '/');
993
994         // Did we shorten everything away?
995         if (dstr.empty()) {
996                 // Yes, filename itself is too long.
997                 // Pick the start and the end of the filename.
998                 docstring fstr = from_utf8(onlyFileName(path));
999                 dstr = fstr;
1000                 if (support::truncateWithEllipsis(dstr, threshold / 2))
1001                         dstr += fstr.substr(fstr.length() - threshold / 2 - 2,
1002                                                                 docstring::npos);
1003         }
1004
1005         return from_utf8(os::external_path(prefix + to_utf8(dstr)));
1006 }
1007
1008
1009 #ifdef HAVE_READLINK
1010 bool readLink(FileName const & file, FileName & link)
1011 {
1012         string const encoded = file.toFilesystemEncoding();
1013 #ifdef HAVE_DEF_PATH_MAX
1014         char linkbuffer[PATH_MAX + 1];
1015         ssize_t const nRead = ::readlink(encoded.c_str(),
1016                                      linkbuffer, sizeof(linkbuffer) - 1);
1017         if (nRead <= 0)
1018                 return false;
1019         linkbuffer[nRead] = '\0'; // terminator
1020 #else
1021         vector<char> buf(1024);
1022         int nRead = -1;
1023
1024         while (true) {
1025                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
1026                 if (nRead < 0) {
1027                         return false;
1028                 }
1029                 if (static_cast<size_t>(nRead) < buf.size() - 1) {
1030                         break;
1031                 }
1032                 buf.resize(buf.size() * 2);
1033         }
1034         buf[nRead] = '\0'; // terminator
1035         const char * linkbuffer = &buf[0];
1036 #endif
1037         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
1038         return true;
1039 }
1040 #else
1041 bool readLink(FileName const &, FileName &)
1042 {
1043         return false;
1044 }
1045 #endif
1046
1047
1048 cmd_ret const runCommand(string const & cmd)
1049 {
1050         // FIXME: replace all calls to RunCommand with ForkedCall
1051         // (if the output is not needed) or the code in ISpell.cpp
1052         // (if the output is needed).
1053
1054         // One question is if we should use popen or
1055         // create our own popen based on fork, exec, pipe
1056         // of course the best would be to have a
1057         // pstream (process stream), with the
1058         // variants ipstream, opstream
1059
1060         if (verbose)
1061                 lyxerr << "\nRunning: " << cmd << endl;
1062         else
1063                 LYXERR(Debug::INFO,"Running: " << cmd);
1064
1065 #if defined (_WIN32)
1066         STARTUPINFO startup;
1067         PROCESS_INFORMATION process;
1068         SECURITY_ATTRIBUTES security;
1069         HANDLE in, out;
1070         FILE * inf = 0;
1071         bool err2out = false;
1072         string command;
1073         string const infile = trim(split(cmd, command, '<'), " \"");
1074         command = rtrim(command);
1075         if (suffixIs(command, "2>&1")) {
1076                 command = rtrim(command, "2>&1");
1077                 err2out = true;
1078         }
1079         string const cmdarg = "/d /c \"" + command + "\"";
1080         string const comspec = getEnv("COMSPEC");
1081
1082         security.nLength = sizeof(SECURITY_ATTRIBUTES);
1083         security.bInheritHandle = TRUE;
1084         security.lpSecurityDescriptor = NULL;
1085
1086         if (CreatePipe(&in, &out, &security, 0)) {
1087                 memset(&startup, 0, sizeof(STARTUPINFO));
1088                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
1089
1090                 startup.cb = sizeof(STARTUPINFO);
1091                 startup.dwFlags = STARTF_USESTDHANDLES;
1092
1093                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
1094                 startup.hStdInput = infile.empty()
1095                         ? GetStdHandle(STD_INPUT_HANDLE)
1096                         : CreateFile(infile.c_str(), GENERIC_READ,
1097                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
1098                                 FILE_ATTRIBUTE_NORMAL, NULL);
1099                 startup.hStdOutput = out;
1100
1101                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
1102                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
1103                                 &security, &security, TRUE, CREATE_NO_WINDOW,
1104                                 0, 0, &startup, &process)) {
1105
1106                         CloseHandle(process.hThread);
1107                         int fno = _open_osfhandle((intptr_t)in, _O_RDONLY);
1108                         CloseHandle(out);
1109                         inf = _fdopen(fno, "r");
1110                 }
1111         }
1112 #elif defined (HAVE_POPEN)
1113         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1114 #elif defined (HAVE__POPEN)
1115         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1116 #else
1117 #error No popen() function.
1118 #endif
1119
1120         // (Claus Hentschel) Check if popen was successful ;-)
1121         if (!inf) {
1122                 lyxerr << "RunCommand: could not start child process" << endl;
1123                 return { false, string() };
1124         }
1125
1126         string result;
1127         int c = fgetc(inf);
1128         while (c != EOF) {
1129                 result += static_cast<char>(c);
1130                 c = fgetc(inf);
1131         }
1132
1133 #if defined (_WIN32)
1134         WaitForSingleObject(process.hProcess, INFINITE);
1135         DWORD pret;
1136         BOOL success = GetExitCodeProcess(process.hProcess, &pret);
1137         bool valid = (pret == 0) && success;
1138         if (!infile.empty())
1139                 CloseHandle(startup.hStdInput);
1140         CloseHandle(process.hProcess);
1141         if (fclose(inf) != 0)
1142                 valid = false;
1143 #elif defined (HAVE_PCLOSE)
1144         int const pret = pclose(inf);
1145         bool const valid = (pret != -1);
1146 #elif defined (HAVE__PCLOSE)
1147         int const pret = _pclose(inf);
1148         bool const valid = (pret != -1);
1149 #else
1150 #error No pclose() function.
1151 #endif
1152
1153         if (!valid)
1154                 perror("RunCommand: could not terminate child process");
1155
1156         return { valid, result };
1157 }
1158
1159
1160 FileName const findtexfile(string const & fil, string const & /*format*/,
1161                                                    bool const onlykpse)
1162 {
1163         /* There is no problem to extend this function to use other
1164            methods to look for files. It could be setup to look
1165            in environment paths and also if wanted as a last resort
1166            to a recursive find. One of the easier extensions would
1167            perhaps be to use the LyX file lookup methods. But! I am
1168            going to implement this until I see some demand for it.
1169            Lgb
1170         */
1171
1172         // If the file can be found directly, we just return a
1173         // absolute path version of it.
1174         if (!onlykpse) {
1175                 FileName const absfile(makeAbsPath(fil));
1176                 if (absfile.exists())
1177                         return absfile;
1178         }
1179
1180         // Now we try to find it using kpsewhich.
1181         // It seems from the kpsewhich manual page that it is safe to use
1182         // kpsewhich without --format: "When the --format option is not
1183         // given, the search path used when looking for a file is inferred
1184         // from the name given, by looking for a known extension. If no
1185         // known extension is found, the search path for TeX source files
1186         // is used."
1187         // However, we want to take advantage of the format sine almost all
1188         // the different formats has environment variables that can be used
1189         // to control which paths to search. f.ex. bib looks in
1190         // BIBINPUTS and TEXBIB. Small list follows:
1191         // bib - BIBINPUTS, TEXBIB
1192         // bst - BSTINPUTS
1193         // graphic/figure - TEXPICTS, TEXINPUTS
1194         // ist - TEXINDEXSTYLE, INDEXSTYLE
1195         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1196         // tex - TEXINPUTS
1197         // tfm - TFMFONTS, TEXFONTS
1198         // This means that to use kpsewhich in the best possible way we
1199         // should help it by setting additional path in the approp. envir.var.
1200         string const kpsecmd = "kpsewhich " + fil;
1201
1202         cmd_ret const c = runCommand(kpsecmd);
1203
1204         LYXERR(Debug::LATEX, "kpse status = " << c.valid << '\n'
1205                  << "kpse result = `" << rtrim(c.result, "\n\r") << '\'');
1206         if (c.valid)
1207                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.result)), "\n\r"));
1208         else
1209                 return FileName();
1210 }
1211
1212
1213 int compare_timestamps(FileName const & file1, FileName const & file2)
1214 {
1215         // If the original is newer than the copy, then copy the original
1216         // to the new directory.
1217
1218         int cmp = 0;
1219         if (file1.exists() && file2.exists()) {
1220                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1221                 if (tmp != 0)
1222                         cmp = tmp > 0 ? 1 : -1;
1223
1224         } else if (file1.exists()) {
1225                 cmp = 1;
1226         } else if (file2.exists()) {
1227                 cmp = -1;
1228         }
1229
1230         return cmp;
1231 }
1232
1233
1234 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1235 {
1236         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1237         if (script.empty()) {
1238                 LYXERR0("Could not find bind file conversion "
1239                                 "script prefs2prefs.py.");
1240                 return false;
1241         }
1242
1243         ostringstream command;
1244         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1245           << ' ' << (lfuns ? "-l" : "-p") << ' '
1246                 << quoteName(filename.toFilesystemEncoding())
1247                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1248         string const command_str = command.str();
1249
1250         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1251
1252         cmd_ret const ret = runCommand(command_str);
1253         if (!ret.valid) {
1254                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1255                 return false;
1256         }
1257         return true;
1258 }
1259
1260
1261 bool configFileNeedsUpdate(string const & file)
1262 {
1263         // We cannot initialize configure_script directly because the package
1264         // is not initialized yet when static objects are constructed.
1265         static FileName configure_script;
1266         static bool firstrun = true;
1267         if (firstrun) {
1268                 configure_script =
1269                         FileName(addName(package().system_support().absFileName(),
1270                                 "configure.py"));
1271                 firstrun = false;
1272         }
1273
1274         FileName absfile =
1275                 FileName(addName(package().user_support().absFileName(), file));
1276         return !absfile.exists()
1277                 || configure_script.lastModified() > absfile.lastModified();
1278 }
1279
1280
1281 int fileLock(const char * lock_file)
1282 {
1283         int fd = -1;
1284 #if defined(HAVE_LOCKF)
1285         fd = open(lock_file, O_CREAT|O_APPEND|O_SYNC|O_RDWR, 0666);
1286         if (fd == -1)
1287                 return -1;
1288         if (lockf(fd, F_LOCK, 0) != 0) {
1289                 close(fd);
1290                 return -1;
1291         }
1292 #endif
1293         return fd;
1294 }
1295
1296
1297 void fileUnlock(int fd, const char * /* lock_file*/)
1298 {
1299 #if defined(HAVE_LOCKF)
1300         if (fd >= 0) {
1301                 if (lockf(fd, F_ULOCK, 0))
1302                         LYXERR0("Can't unlock the file.");
1303                 close(fd);
1304         }
1305 #endif
1306 }
1307
1308 } //namespace support
1309 } // namespace lyx