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