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