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