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