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