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