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