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