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