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