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