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