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