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