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