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