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