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