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