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