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