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