]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Refactor runCommand
[lyx.git] / src / support / filetools.cpp
1 /**
2  * \file filetools.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "LyX.h"
25 #include "LyXRC.h"
26
27 #include "support/filetools.h"
28
29 #include "support/convert.h"
30 #include "support/debug.h"
31 #include "support/environment.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/os.h"
35 #include "support/Messages.h"
36 #include "support/Package.h"
37 #include "support/PathChanger.h"
38 #include "support/Systemcall.h"
39 #include "support/qstring_helpers.h"
40 #include "support/TempFile.h"
41
42 #include <QDir>
43
44 #include "support/lassert.h"
45 #include "support/regex.h"
46
47 #include <fcntl.h>
48 #ifdef HAVE_MAGIC_H
49 #include <magic.h>
50 #endif
51 #ifdef HAVE_UNISTD_H
52 #include <unistd.h>
53 #endif
54
55 #include <cerrno>
56 #include <climits>
57 #include <cstdlib>
58 #include <cstdio>
59
60 #include <utility>
61 #include <fstream>
62 #include <sstream>
63 #include <vector>
64
65 #if defined (_WIN32)
66 #include <io.h>
67 #include <windows.h>
68 #endif
69
70 using namespace std;
71
72 #define USE_QPROCESS
73
74 namespace lyx {
75 namespace support {
76
77 bool isLyXFileName(string const & filename)
78 {
79         return suffixIs(ascii_lowercase(filename), ".lyx");
80 }
81
82
83 bool isSGMLFileName(string const & filename)
84 {
85         return suffixIs(ascii_lowercase(filename), ".sgml");
86 }
87
88
89 bool isValidLaTeXFileName(string const & filename)
90 {
91         string const invalid_chars("#%\"");
92         return filename.find_first_of(invalid_chars) == string::npos;
93 }
94
95
96 bool isValidDVIFileName(string const & filename)
97 {
98         string const invalid_chars("${}()[]^");
99         return filename.find_first_of(invalid_chars) == string::npos;
100 }
101
102
103 bool isBinaryFile(FileName const & filename)
104 {
105         bool isbinary = false;
106         if (filename.empty() || !filename.exists())
107                 return isbinary;
108
109 #ifdef HAVE_MAGIC_H
110         magic_t magic_cookie = magic_open(MAGIC_MIME_ENCODING);
111         if (magic_cookie) {
112                 bool detected = true;
113                 if (magic_load(magic_cookie, 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 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         if (tmp_names_.find(abs) != tmp_names_.end())
501                 tmp_names_.erase(abs);
502         fn.removeFile();
503 }
504
505
506 static FileName createTmpDir(FileName const & tempdir, string const & mask)
507 {
508         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
509                 << "createTmpDir:    mask=`" << mask << '\'');
510
511         QFileInfo tmp_fi(QDir(toqstr(tempdir.absFileName())), toqstr(mask));
512         FileName const tmpfl =
513                 tempFileName(FileName(fromqstr(tmp_fi.absolutePath())),
514                              fromqstr(tmp_fi.fileName()) + ".XXXXXXXXXXXX", true);
515
516         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
517                 LYXERR0("LyX could not create temporary directory in " << tempdir
518                         << "'");
519                 return FileName();
520         }
521
522         return tmpfl;
523 }
524
525
526 FileName const createLyXTmpDir(FileName const & deflt)
527 {
528         if (deflt.empty() || deflt == package().system_temp_dir())
529                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
530
531         if (deflt.createDirectory(0777))
532                 return deflt;
533
534         if (deflt.isDirWritable()) {
535                 // deflt could not be created because it
536                 // did exist already, so let's create our own
537                 // dir inside deflt.
538                 return createTmpDir(deflt, "lyx_tmpdir");
539         } else {
540                 // some other error occurred.
541                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
542         }
543 }
544
545
546 // Strip filename from path name
547 string const onlyPath(string const & filename)
548 {
549         // If empty filename, return empty
550         if (filename.empty())
551                 return filename;
552
553         // Find last / or start of filename
554         size_t j = filename.rfind('/');
555         return j == string::npos ? "./" : filename.substr(0, j + 1);
556 }
557
558
559 // Convert relative path into absolute path based on a basepath.
560 // If relpath is absolute, just use that.
561 // If basepath is empty, use CWD as base.
562 // Note that basePath can be a relative path, in the sense that it may
563 // not begin with "/" (e.g.), but it should NOT contain such constructs
564 // as "/../".
565 // FIXME It might be nice if the code didn't simply assume that.
566 FileName const makeAbsPath(string const & relPath, string const & basePath)
567 {
568         // checks for already absolute path
569         if (FileName::isAbsolute(relPath))
570                 return FileName(relPath);
571
572         // Copies given paths
573         string tempRel = os::internal_path(relPath);
574         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
575         tempRel = subst(tempRel, "//", "/");
576
577         string tempBase;
578
579         if (FileName::isAbsolute(basePath))
580                 tempBase = basePath;
581         else
582                 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
583
584         // Handle /./ at the end of the path
585         while (suffixIs(tempBase, "/./"))
586                 tempBase.erase(tempBase.length() - 2);
587
588         // processes relative path
589         string rTemp = tempRel;
590         string temp;
591
592         // Check for a leading "~"
593         // Split by first /
594         rTemp = split(rTemp, temp, '/');
595         if (temp == "~") {
596                 tempBase = Package::get_home_dir().absFileName();
597                 tempRel = rTemp;
598         }
599
600         rTemp = tempRel;
601         while (!rTemp.empty()) {
602                 // Split by next /
603                 rTemp = split(rTemp, temp, '/');
604
605                 if (temp == ".") continue;
606                 if (temp == "..") {
607                         // Remove one level of TempBase
608                         if (tempBase.length() <= 1) {
609                                 //this is supposed to be an absolute path, so...
610                                 tempBase = "/";
611                                 continue;
612                         }
613                         //erase a trailing slash if there is one
614                         if (suffixIs(tempBase, "/"))
615                                 tempBase.erase(tempBase.length() - 1, string::npos);
616
617                         string::size_type i = tempBase.length() - 1;
618                         while (i > 0 && tempBase[i] != '/')
619                                 --i;
620                         if (i > 0)
621                                 tempBase.erase(i, string::npos);
622                         else
623                                 tempBase = '/';
624                 } else if (temp.empty() && !rTemp.empty()) {
625                                 tempBase = os::current_root() + rTemp;
626                                 rTemp.erase();
627                 } else {
628                         // Add this piece to TempBase
629                         if (!suffixIs(tempBase, '/'))
630                                 tempBase += '/';
631                         tempBase += temp;
632                 }
633         }
634
635         // returns absolute path
636         return FileName(tempBase);
637 }
638
639
640 // Correctly append filename to the pathname.
641 // If pathname is '.', then don't use pathname.
642 // Chops any path of filename.
643 string const addName(string const & path, string const & fname)
644 {
645         string const basename = onlyFileName(fname);
646         string buf;
647
648         if (path != "." && path != "./" && !path.empty()) {
649                 buf = os::internal_path(path);
650                 if (!suffixIs(buf, '/'))
651                         buf += '/';
652         }
653
654         return buf + basename;
655 }
656
657
658 // Strips path from filename
659 string const onlyFileName(string const & fname)
660 {
661         if (fname.empty())
662                 return fname;
663
664         string::size_type j = fname.rfind('/');
665         if (j == string::npos) // no '/' in fname
666                 return fname;
667
668         // Strip to basename
669         return fname.substr(j + 1);
670 }
671
672
673 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
674 string const replaceEnvironmentPath(string const & path)
675 {
676         // ${VAR} is defined as
677         // $\{[A-Za-z_][A-Za-z_0-9]*\}
678         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
679
680         // $VAR is defined as:
681         // $[A-Za-z_][A-Za-z_0-9]*
682         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
683
684         // Coverity thinks that the regex constructor can return an
685         // exception. We know that it is not true since our regex are
686         // hardcoded, but we have to protect against that nevertheless.
687         try {
688                 static regex const envvar_br_re("(.*)" + envvar_br + "(.*)");
689                 static regex const envvar_re("(.*)" + envvar + "(.*)");
690                 string result = path;
691                 while (1) {
692                         smatch what;
693                         if (!regex_match(result, what, envvar_br_re)) {
694                                 if (!regex_match(result, what, envvar_re))
695                                         break;
696                         }
697                         string env_var = getEnv(what.str(2));
698                         result = what.str(1) + env_var + what.str(3);
699                 }
700                 return result;
701         } catch (exception const & e) {
702                 LYXERR0("Something is very wrong: " << e.what());
703                 return path;
704         }
705 }
706
707
708 // Return a command prefix for setting the environment of the TeX engine.
709 string latexEnvCmdPrefix(string const & path, string const & lpath)
710 {
711         bool use_lpath = !(lpath.empty() || lpath == "." || lpath == "./");
712
713         if (path.empty() || (lyxrc.texinputs_prefix.empty() && !use_lpath))
714                 return string();
715
716         string texinputs_prefix = lyxrc.texinputs_prefix.empty() ? string()
717                 : os::latex_path_list(
718                         replaceCurdirPath(path, lyxrc.texinputs_prefix));
719         string const allother_prefix = os::latex_path_list(path);
720         string const sep = string(1, os::path_separator(os::TEXENGINE));
721         string const texinputs = getEnv("TEXINPUTS");
722         string const bibinputs = getEnv("BIBINPUTS");
723         string const bstinputs = getEnv("BSTINPUTS");
724         string const texfonts = getEnv("TEXFONTS");
725
726         if (use_lpath) {
727                 string const abslpath = FileName::isAbsolute(lpath)
728                         ? os::latex_path(lpath)
729                         : os::latex_path(FileName(path + "/" + lpath).realPath());
730                 if (texinputs_prefix.empty())
731                         texinputs_prefix = abslpath;
732                 else if (suffixIs(texinputs_prefix, sep))
733                         texinputs_prefix.append(abslpath + sep);
734                 else
735                         texinputs_prefix.append(sep + abslpath);
736         }
737
738         if (os::shell() == os::UNIX)
739                 return "env TEXINPUTS=\"." + sep + texinputs_prefix
740                                            + sep + texinputs + "\" "
741                          + "BIBINPUTS=\"." + sep + allother_prefix
742                                            + sep + bibinputs + "\" "
743                          + "BSTINPUTS=\"." + sep + allother_prefix
744                                            + sep + bstinputs + "\" "
745                          + "TEXFONTS=\"."  + sep + allother_prefix
746                                            + sep + texfonts + "\" ";
747         else
748                 // NOTE: the dummy blank dirs are necessary to force the
749                 //       QProcess parser to quote the argument (see bug 9453)
750                 return "cmd /d /c set \"TEXINPUTS=." + sep + " "
751                                                 + sep + texinputs_prefix
752                                                 + sep + texinputs + "\" & "
753                                + "set \"BIBINPUTS=." + sep + " "
754                                                 + sep + allother_prefix
755                                                 + sep + bibinputs + "\" & "
756                                + "set \"BSTINPUTS=." + sep + " "
757                                                 + sep + allother_prefix
758                                                 + sep + bstinputs + "\" & "
759                                + "set \"TEXFONTS=."  + sep + " "
760                                                 + sep + allother_prefix
761                                                 + sep + texfonts + "\" & ";
762 }
763
764
765 // Replace current directory in all elements of a path list with a given path.
766 string const replaceCurdirPath(string const & path, string const & pathlist)
767 {
768         string const oldpathlist = replaceEnvironmentPath(pathlist);
769         char const sep = os::path_separator();
770         string newpathlist;
771
772         for (size_t i = 0, k = 0; i != string::npos; k = i) {
773                 i = oldpathlist.find(sep, i);
774                 string p = oldpathlist.substr(k, i - k);
775                 if (FileName::isAbsolute(p)) {
776                         newpathlist += p;
777                 } else if (i > k) {
778                         size_t offset = 0;
779                         if (p == ".") {
780                                 offset = 1;
781                         } else if (prefixIs(p, "./")) {
782                                 offset = 2;
783                                 while (p[offset] == '/')
784                                         ++offset;
785                         }
786                         newpathlist += addPath(path, p.substr(offset));
787                         if (suffixIs(p, "//"))
788                                 newpathlist += '/';
789                 }
790                 if (i != string::npos) {
791                         newpathlist += sep;
792                         // Stop here if the last element is empty
793                         if (++i == oldpathlist.length())
794                                 break;
795                 }
796         }
797         return newpathlist;
798 }
799
800
801 // Make relative path out of two absolute paths
802 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
803 // Makes relative path out of absolute path. If it is deeper than basepath,
804 // it's easy. If basepath and abspath share something (they are all deeper
805 // than some directory), it'll be rendered using ..'s. If they are completely
806 // different, then the absolute path will be used as relative path.
807 {
808         docstring::size_type const abslen = abspath.length();
809         docstring::size_type const baselen = basepath.length();
810
811         docstring::size_type i = os::common_path(abspath, basepath);
812
813         if (i == 0) {
814                 // actually no match - cannot make it relative
815                 return abspath;
816         }
817
818         // Count how many dirs there are in basepath above match
819         // and append as many '..''s into relpath
820         docstring buf;
821         docstring::size_type j = i;
822         while (j < baselen) {
823                 if (basepath[j] == '/') {
824                         if (j + 1 == baselen)
825                                 break;
826                         buf += "../";
827                 }
828                 ++j;
829         }
830
831         // Append relative stuff from common directory to abspath
832         if (abspath[i] == '/')
833                 ++i;
834         for (; i < abslen; ++i)
835                 buf += abspath[i];
836         // Remove trailing /
837         if (suffixIs(buf, '/'))
838                 buf.erase(buf.length() - 1);
839         // Substitute empty with .
840         if (buf.empty())
841                 buf = '.';
842         return buf;
843 }
844
845
846 // Append sub-directory(ies) to a path in an intelligent way
847 string const addPath(string const & path, string const & path_2)
848 {
849         string buf;
850         string const path2 = os::internal_path(path_2);
851
852         if (!path.empty() && path != "." && path != "./") {
853                 buf = os::internal_path(path);
854                 if (path[path.length() - 1] != '/')
855                         buf += '/';
856         }
857
858         if (!path2.empty()) {
859                 string::size_type const p2start = path2.find_first_not_of('/');
860                 string::size_type const p2end = path2.find_last_not_of('/');
861                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
862                 buf += tmp + '/';
863         }
864         return buf;
865 }
866
867
868 string const changeExtension(string const & oldname, string const & extension)
869 {
870         string::size_type const last_slash = oldname.rfind('/');
871         string::size_type last_dot = oldname.rfind('.');
872         if (last_dot < last_slash && last_slash != string::npos)
873                 last_dot = string::npos;
874
875         string ext;
876         // Make sure the extension starts with a dot
877         if (!extension.empty() && extension[0] != '.')
878                 ext= '.' + extension;
879         else
880                 ext = extension;
881
882         return os::internal_path(oldname.substr(0, last_dot) + ext);
883 }
884
885
886 string const removeExtension(string const & name)
887 {
888         return changeExtension(name, string());
889 }
890
891
892 string const addExtension(string const & name, string const & extension)
893 {
894         if (!extension.empty() && extension[0] != '.')
895                 return name + '.' + extension;
896         return name + extension;
897 }
898
899
900 /// Return the extension of the file (not including the .)
901 string const getExtension(string const & name)
902 {
903         string::size_type const last_slash = name.rfind('/');
904         string::size_type const last_dot = name.rfind('.');
905         if (last_dot != string::npos &&
906             (last_slash == string::npos || last_dot > last_slash))
907                 return name.substr(last_dot + 1,
908                                    name.length() - (last_dot + 1));
909         else
910                 return string();
911 }
912
913
914 string const unzippedFileName(string const & zipped_file)
915 {
916         string const ext = getExtension(zipped_file);
917         if (ext == "gz" || ext == "z" || ext == "Z")
918                 return changeExtension(zipped_file, string());
919         else if (ext == "svgz")
920                 return changeExtension(zipped_file, "svg");
921         return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
922 }
923
924
925 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
926 {
927         FileName const tempfile = FileName(unzipped_file.empty() ?
928                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
929                 unzipped_file);
930         // Run gunzip
931         string const command = "gunzip -c \"" +
932                 zipped_file.toFilesystemEncoding() + "\" > \"" +
933                 tempfile.toFilesystemEncoding() + "\"";
934         Systemcall one;
935         one.startscript(Systemcall::Wait, command);
936         // test that command was executed successfully (anon)
937         // yes, please do. (Lgb)
938         return tempfile;
939 }
940
941
942 docstring const makeDisplayPath(string const & path, unsigned int threshold)
943 {
944         string str = path;
945
946         // Recode URL encoded chars.
947         str = from_percent_encoding(str);
948
949         // If file is from LyXDir, display it as if it were relative.
950         string const system = package().system_support().absFileName();
951         if (prefixIs(str, system) && str != system)
952                 return from_utf8("[" + str.erase(0, system.length()) + "]");
953
954         // replace /home/blah with ~/
955         string const home = Package::get_home_dir().absFileName();
956         if (!home.empty() && prefixIs(str, home))
957                 str = subst(str, home, "~");
958
959         if (str.length() <= threshold)
960                 return from_utf8(os::external_path(str));
961
962         string const prefix = ".../";
963         docstring dstr = from_utf8(str);
964         docstring temp;
965
966         while (dstr.length() > threshold)
967                 dstr = split(dstr, temp, '/');
968
969         // Did we shorten everything away?
970         if (dstr.empty()) {
971                 // Yes, filename itself is too long.
972                 // Pick the start and the end of the filename.
973                 docstring fstr = from_utf8(onlyFileName(path));
974                 dstr = fstr;
975                 if (support::truncateWithEllipsis(dstr, threshold / 2))
976                         dstr += fstr.substr(fstr.length() - threshold / 2 - 2,
977                                                                 docstring::npos);
978         }
979
980         return from_utf8(os::external_path(prefix + to_utf8(dstr)));
981 }
982
983
984 #ifdef HAVE_READLINK
985 bool readLink(FileName const & file, FileName & link)
986 {
987         string const encoded = file.toFilesystemEncoding();
988 #ifdef HAVE_DEF_PATH_MAX
989         char linkbuffer[PATH_MAX + 1];
990         ssize_t const nRead = ::readlink(encoded.c_str(),
991                                      linkbuffer, sizeof(linkbuffer) - 1);
992         if (nRead <= 0)
993                 return false;
994         linkbuffer[nRead] = '\0'; // terminator
995 #else
996         vector<char> buf(1024);
997         int nRead = -1;
998
999         while (true) {
1000                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
1001                 if (nRead < 0) {
1002                         return false;
1003                 }
1004                 if (static_cast<size_t>(nRead) < buf.size() - 1) {
1005                         break;
1006                 }
1007                 buf.resize(buf.size() * 2);
1008         }
1009         buf[nRead] = '\0'; // terminator
1010         const char * linkbuffer = &buf[0];
1011 #endif
1012         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
1013         return true;
1014 }
1015 #else
1016 bool readLink(FileName const &, FileName &)
1017 {
1018         return false;
1019 }
1020 #endif
1021
1022
1023 cmd_ret const runCommand(string const & cmd)
1024 {
1025         // FIXME: replace all calls to RunCommand with ForkedCall
1026         // (if the output is not needed) or the code in ISpell.cpp
1027         // (if the output is needed).
1028
1029         // One question is if we should use popen or
1030         // create our own popen based on fork, exec, pipe
1031         // of course the best would be to have a
1032         // pstream (process stream), with the
1033         // variants ipstream, opstream
1034
1035         if (verbose)
1036                 lyxerr << "\nRunning: " << cmd << endl;
1037         else
1038                 LYXERR(Debug::INFO,"Running: " << cmd);
1039
1040 #if defined (_WIN32)
1041         STARTUPINFO startup;
1042         PROCESS_INFORMATION process;
1043         SECURITY_ATTRIBUTES security;
1044         HANDLE in, out;
1045         FILE * inf = 0;
1046         bool err2out = false;
1047         string command;
1048         string const infile = trim(split(cmd, command, '<'), " \"");
1049         command = rtrim(command);
1050         if (suffixIs(command, "2>&1")) {
1051                 command = rtrim(command, "2>&1");
1052                 err2out = true;
1053         }
1054         string const cmdarg = "/d /c \"" + command + "\"";
1055         string const comspec = getEnv("COMSPEC");
1056
1057         security.nLength = sizeof(SECURITY_ATTRIBUTES);
1058         security.bInheritHandle = TRUE;
1059         security.lpSecurityDescriptor = NULL;
1060
1061         if (CreatePipe(&in, &out, &security, 0)) {
1062                 memset(&startup, 0, sizeof(STARTUPINFO));
1063                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
1064
1065                 startup.cb = sizeof(STARTUPINFO);
1066                 startup.dwFlags = STARTF_USESTDHANDLES;
1067
1068                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
1069                 startup.hStdInput = infile.empty()
1070                         ? GetStdHandle(STD_INPUT_HANDLE)
1071                         : CreateFile(infile.c_str(), GENERIC_READ,
1072                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
1073                                 FILE_ATTRIBUTE_NORMAL, NULL);
1074                 startup.hStdOutput = out;
1075
1076                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
1077                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
1078                                 &security, &security, TRUE, CREATE_NO_WINDOW,
1079                                 0, 0, &startup, &process)) {
1080
1081                         CloseHandle(process.hThread);
1082                         int fno = _open_osfhandle((intptr_t)in, _O_RDONLY);
1083                         CloseHandle(out);
1084                         inf = _fdopen(fno, "r");
1085                 }
1086         }
1087 #elif defined (HAVE_POPEN)
1088         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1089 #elif defined (HAVE__POPEN)
1090         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1091 #else
1092 #error No popen() function.
1093 #endif
1094
1095         // (Claus Hentschel) Check if popen was successful ;-)
1096         if (!inf) {
1097                 lyxerr << "RunCommand:: could not start child process" << endl;
1098                 return { false, string() };
1099         }
1100
1101         string result;
1102         int c = fgetc(inf);
1103         while (c != EOF) {
1104                 result += static_cast<char>(c);
1105                 c = fgetc(inf);
1106         }
1107
1108 #if defined (_WIN32)
1109         WaitForSingleObject(process.hProcess, INFINITE);
1110         DWORD pret;
1111         BOOL success = GetExitCodeProcess(process.hProcess, &pret);
1112         bool valid = (pret == 0) && success;
1113         if (!infile.empty())
1114                 CloseHandle(startup.hStdInput);
1115         CloseHandle(process.hProcess);
1116         if (fclose(inf) != 0)
1117                 valid = false;
1118 #elif defined (HAVE_PCLOSE)
1119         int const pret = pclose(inf);
1120         bool const valid = (pret != -1);
1121 #elif defined (HAVE__PCLOSE)
1122         int const pret = _pclose(inf);
1123         bool const valid = (pret != -1);
1124 #else
1125 #error No pclose() function.
1126 #endif
1127
1128         if (!valid)
1129                 perror("RunCommand:: could not terminate child process");
1130
1131         return { valid, result };
1132 }
1133
1134
1135 FileName const findtexfile(string const & fil, string const & /*format*/,
1136                                                    bool const onlykpse)
1137 {
1138         /* There is no problem to extend this function too use other
1139            methods to look for files. It could be setup to look
1140            in environment paths and also if wanted as a last resort
1141            to a recursive find. One of the easier extensions would
1142            perhaps be to use the LyX file lookup methods. But! I am
1143            going to implement this until I see some demand for it.
1144            Lgb
1145         */
1146
1147         // If the file can be found directly, we just return a
1148         // absolute path version of it.
1149         if (!onlykpse) {
1150                 FileName const absfile(makeAbsPath(fil));
1151                 if (absfile.exists())
1152                         return absfile;
1153         }
1154
1155         // Now we try to find it using kpsewhich.
1156         // It seems from the kpsewhich manual page that it is safe to use
1157         // kpsewhich without --format: "When the --format option is not
1158         // given, the search path used when looking for a file is inferred
1159         // from the name given, by looking for a known extension. If no
1160         // known extension is found, the search path for TeX source files
1161         // is used."
1162         // However, we want to take advantage of the format sine almost all
1163         // the different formats has environment variables that can be used
1164         // to control which paths to search. f.ex. bib looks in
1165         // BIBINPUTS and TEXBIB. Small list follows:
1166         // bib - BIBINPUTS, TEXBIB
1167         // bst - BSTINPUTS
1168         // graphic/figure - TEXPICTS, TEXINPUTS
1169         // ist - TEXINDEXSTYLE, INDEXSTYLE
1170         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1171         // tex - TEXINPUTS
1172         // tfm - TFMFONTS, TEXFONTS
1173         // This means that to use kpsewhich in the best possible way we
1174         // should help it by setting additional path in the approp. envir.var.
1175         string const kpsecmd = "kpsewhich " + fil;
1176
1177         cmd_ret const c = runCommand(kpsecmd);
1178
1179         LYXERR(Debug::LATEX, "kpse status = " << c.valid << '\n'
1180                  << "kpse result = `" << rtrim(c.result, "\n\r") << '\'');
1181         if (c.valid)
1182                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.result)), "\n\r"));
1183         else
1184                 return FileName();
1185 }
1186
1187
1188 int compare_timestamps(FileName const & file1, FileName const & file2)
1189 {
1190         // If the original is newer than the copy, then copy the original
1191         // to the new directory.
1192
1193         int cmp = 0;
1194         if (file1.exists() && file2.exists()) {
1195                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1196                 if (tmp != 0)
1197                         cmp = tmp > 0 ? 1 : -1;
1198
1199         } else if (file1.exists()) {
1200                 cmp = 1;
1201         } else if (file2.exists()) {
1202                 cmp = -1;
1203         }
1204
1205         return cmp;
1206 }
1207
1208
1209 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1210 {
1211         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1212         if (script.empty()) {
1213                 LYXERR0("Could not find bind file conversion "
1214                                 "script prefs2prefs.py.");
1215                 return false;
1216         }
1217
1218         ostringstream command;
1219         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1220           << ' ' << (lfuns ? "-l" : "-p") << ' '
1221                 << quoteName(filename.toFilesystemEncoding())
1222                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1223         string const command_str = command.str();
1224
1225         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1226
1227         cmd_ret const ret = runCommand(command_str);
1228         if (!ret.valid) {
1229                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1230                 return false;
1231         }
1232         return true;
1233 }
1234
1235
1236 bool configFileNeedsUpdate(string const & file)
1237 {
1238         // We cannot initialize configure_script directly because the package
1239         // is not initialized yet when static objects are constructed.
1240         static FileName configure_script;
1241         static bool firstrun = true;
1242         if (firstrun) {
1243                 configure_script =
1244                         FileName(addName(package().system_support().absFileName(),
1245                                 "configure.py"));
1246                 firstrun = false;
1247         }
1248
1249         FileName absfile =
1250                 FileName(addName(package().user_support().absFileName(), file));
1251         return !absfile.exists()
1252                 || configure_script.lastModified() > absfile.lastModified();
1253 }
1254
1255
1256 int fileLock(const char * lock_file)
1257 {
1258         int fd = -1;
1259 #if defined(HAVE_LOCKF)
1260         fd = open(lock_file, O_CREAT|O_APPEND|O_SYNC|O_RDWR, 0666);
1261         if (fd == -1)
1262                 return -1;
1263         if (lockf(fd, F_LOCK, 0) != 0) {
1264                 close(fd);
1265                 return -1;
1266         }
1267 #endif
1268         return fd;
1269 }
1270
1271
1272 void fileUnlock(int fd, const char * /* lock_file*/)
1273 {
1274 #if defined(HAVE_LOCKF)
1275         if (fd >= 0) {
1276                 if (lockf(fd, F_ULOCK, 0))
1277                         LYXERR0("Can't unlock the file.");
1278                 close(fd);
1279         }
1280 #endif
1281 }
1282
1283 } //namespace support
1284 } // namespace lyx