]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
fd04c6a98414e701920683503048745ad97aed20
[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)
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                 quote_style style = quote_shell;
365                 string const python_call = "python -tt";
366                 if (prefixIs(command, python_call) || prefixIs(command, os::python()))
367                         style = quote_python;
368
369                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
370                 string::size_type const size_replace = size_script + 4;
371                 command.replace(pos1, size_replace, quoteName(script, style));
372         }
373
374         return command;
375 }
376
377
378 static FileName createTmpDir(FileName const & tempdir, string const & mask)
379 {
380         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
381                 << "createTmpDir:    mask=`" << mask << '\'');
382
383         FileName const tmpfl = FileName::tempName(tempdir, mask);
384
385         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
386                 LYXERR0("LyX could not create temporary directory in " << tempdir
387                         << "'");
388                 return FileName();
389         }
390
391         return tmpfl;
392 }
393
394
395 FileName const createLyXTmpDir(FileName const & deflt)
396 {
397         if (deflt.empty() || deflt == package().system_temp_dir())
398                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
399
400         if (deflt.createDirectory(0777))
401                 return deflt;
402
403         if (deflt.isDirWritable()) {
404                 // deflt could not be created because it
405                 // did exist already, so let's create our own
406                 // dir inside deflt.
407                 return createTmpDir(deflt, "lyx_tmpdir");
408         } else {
409                 // some other error occured.
410                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
411         }
412 }
413
414
415 // Strip filename from path name
416 string const onlyPath(string const & filename)
417 {
418         // If empty filename, return empty
419         if (filename.empty())
420                 return filename;
421
422         // Find last / or start of filename
423         size_t j = filename.rfind('/');
424         return j == string::npos ? "./" : filename.substr(0, j + 1);
425 }
426
427
428 // Convert relative path into absolute path based on a basepath.
429 // If relpath is absolute, just use that.
430 // If basepath is empty, use CWD as base.
431 // Note that basePath can be a relative path, in the sense that it may
432 // not begin with "/" (e.g.), but it should NOT contain such constructs
433 // as "/../".
434 // FIXME It might be nice if the code didn't simply assume that.
435 FileName const makeAbsPath(string const & relPath, string const & basePath)
436 {
437         // checks for already absolute path
438         if (FileName::isAbsolute(relPath))
439                 return FileName(relPath);
440
441         // Copies given paths
442         string tempRel = os::internal_path(relPath);
443         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
444         tempRel = subst(tempRel, "//", "/");
445
446         string tempBase;
447
448         if (FileName::isAbsolute(basePath))
449                 tempBase = basePath;
450         else
451                 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
452
453         // Handle /./ at the end of the path
454         while (suffixIs(tempBase, "/./"))
455                 tempBase.erase(tempBase.length() - 2);
456
457         // processes relative path
458         string rTemp = tempRel;
459         string temp;
460
461         // Check for a leading "~"
462         // Split by first /
463         rTemp = split(rTemp, temp, '/');
464         if (temp == "~") {
465                 tempBase = Package::get_home_dir().absFileName();
466                 tempRel = rTemp;
467         }
468
469         rTemp = tempRel;
470         while (!rTemp.empty()) {
471                 // Split by next /
472                 rTemp = split(rTemp, temp, '/');
473
474                 if (temp == ".") continue;
475                 if (temp == "..") {
476                         // Remove one level of TempBase
477                         if (tempBase.length() <= 1) {
478                                 //this is supposed to be an absolute path, so...
479                                 tempBase = "/";
480                                 continue;
481                         }
482                         //erase a trailing slash if there is one
483                         if (suffixIs(tempBase, "/"))
484                                 tempBase.erase(tempBase.length() - 1, string::npos);
485
486                         string::size_type i = tempBase.length() - 1;
487                         while (i > 0 && tempBase[i] != '/')
488                                 --i;
489                         if (i > 0)
490                                 tempBase.erase(i, string::npos);
491                         else
492                                 tempBase = '/';
493                 } else if (temp.empty() && !rTemp.empty()) {
494                                 tempBase = os::current_root() + rTemp;
495                                 rTemp.erase();
496                 } else {
497                         // Add this piece to TempBase
498                         if (!suffixIs(tempBase, '/'))
499                                 tempBase += '/';
500                         tempBase += temp;
501                 }
502         }
503
504         // returns absolute path
505         return FileName(tempBase);
506 }
507
508
509 // Correctly append filename to the pathname.
510 // If pathname is '.', then don't use pathname.
511 // Chops any path of filename.
512 string const addName(string const & path, string const & fname)
513 {
514         string const basename = onlyFileName(fname);
515         string buf;
516
517         if (path != "." && path != "./" && !path.empty()) {
518                 buf = os::internal_path(path);
519                 if (!suffixIs(path, '/'))
520                         buf += '/';
521         }
522
523         return buf + basename;
524 }
525
526
527 // Strips path from filename
528 string const onlyFileName(string const & fname)
529 {
530         if (fname.empty())
531                 return fname;
532
533         string::size_type j = fname.rfind('/');
534         if (j == string::npos) // no '/' in fname
535                 return fname;
536
537         // Strip to basename
538         return fname.substr(j + 1);
539 }
540
541
542 // Create absolute path. If impossible, don't do anything
543 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
544 string const expandPath(string const & path)
545 {
546         // checks for already absolute path
547         string rTemp = replaceEnvironmentPath(path);
548         if (FileName::isAbsolute(rTemp))
549                 return rTemp;
550
551         string temp;
552         string const copy = rTemp;
553
554         // Split by next /
555         rTemp = split(rTemp, temp, '/');
556
557         if (temp == ".")
558                 return FileName::getcwd().absFileName() + '/' + rTemp;
559
560         if (temp == "~")
561                 return Package::get_home_dir().absFileName() + '/' + rTemp;
562
563         if (temp == "..")
564                 return makeAbsPath(copy).absFileName();
565
566         // Don't know how to handle this
567         return copy;
568 }
569
570
571 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
572 string const replaceEnvironmentPath(string const & path)
573 {
574         // ${VAR} is defined as
575         // $\{[A-Za-z_][A-Za-z_0-9]*\}
576         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
577
578         // $VAR is defined as:
579         // $[A-Za-z_][A-Za-z_0-9]*
580         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
581
582         static regex const envvar_br_re("(.*)" + envvar_br + "(.*)");
583         static regex const envvar_re("(.*)" + envvar + "(.*)");
584         string result = path;
585         while (1) {
586                 smatch what;
587                 if (!regex_match(result, what, envvar_br_re)) {
588                         if (!regex_match(result, what, envvar_re))
589                                 break;
590                 }
591                 string env_var = getEnv(what.str(2));
592                 result = what.str(1) + env_var + what.str(3);
593         }
594         return result;
595 }
596
597
598 // Return a command prefix for setting the environment of the TeX engine.
599 string latexEnvCmdPrefix(string const & path)
600 {
601         if (path.empty() || lyxrc.texinputs_prefix.empty())
602                 return string();
603
604         string const texinputs_prefix = os::latex_path_list(
605                         replaceCurdirPath(path, lyxrc.texinputs_prefix));
606         string const sep = string(1, os::path_separator(os::TEXENGINE));
607         string const texinputs = getEnv("TEXINPUTS");
608
609         if (os::shell() == os::UNIX)
610                 return "env TEXINPUTS=\"." + sep + texinputs_prefix
611                                           + sep + texinputs + "\" ";
612         else
613                 return "cmd /d /c set TEXINPUTS=." + sep + texinputs_prefix
614                                                    + sep + texinputs + "&";
615 }
616
617
618 // Replace current directory in all elements of a path list with a given path.
619 string const replaceCurdirPath(string const & path, string const & pathlist)
620 {
621         string const oldpathlist = replaceEnvironmentPath(pathlist);
622         char const sep = os::path_separator();
623         string newpathlist;
624
625         for (size_t i = 0, k = 0; i != string::npos; k = i) {
626                 i = oldpathlist.find(sep, i);
627                 string p = oldpathlist.substr(k, i - k);
628                 if (FileName::isAbsolute(p)) {
629                         newpathlist += p;
630                 } else if (i > k) {
631                         size_t offset = 0;
632                         if (p == ".") {
633                                 offset = 1;
634                         } else if (prefixIs(p, "./")) {
635                                 offset = 2;
636                                 while (p[offset] == '/')
637                                         ++offset;
638                         }
639                         newpathlist += addPath(path, p.substr(offset));
640                         if (suffixIs(p, "//"))
641                                 newpathlist += '/';
642                 }
643                 if (i != string::npos) {
644                         newpathlist += sep;
645                         // Stop here if the last element is empty 
646                         if (++i == oldpathlist.length())
647                                 break;
648                 }
649         }
650         return newpathlist;
651 }
652
653
654 // Make relative path out of two absolute paths
655 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
656 // Makes relative path out of absolute path. If it is deeper than basepath,
657 // it's easy. If basepath and abspath share something (they are all deeper
658 // than some directory), it'll be rendered using ..'s. If they are completely
659 // different, then the absolute path will be used as relative path.
660 {
661         docstring::size_type const abslen = abspath.length();
662         docstring::size_type const baselen = basepath.length();
663
664         docstring::size_type i = os::common_path(abspath, basepath);
665
666         if (i == 0) {
667                 // actually no match - cannot make it relative
668                 return abspath;
669         }
670
671         // Count how many dirs there are in basepath above match
672         // and append as many '..''s into relpath
673         docstring buf;
674         docstring::size_type j = i;
675         while (j < baselen) {
676                 if (basepath[j] == '/') {
677                         if (j + 1 == baselen)
678                                 break;
679                         buf += "../";
680                 }
681                 ++j;
682         }
683
684         // Append relative stuff from common directory to abspath
685         if (abspath[i] == '/')
686                 ++i;
687         for (; i < abslen; ++i)
688                 buf += abspath[i];
689         // Remove trailing /
690         if (suffixIs(buf, '/'))
691                 buf.erase(buf.length() - 1);
692         // Substitute empty with .
693         if (buf.empty())
694                 buf = '.';
695         return buf;
696 }
697
698
699 // Append sub-directory(ies) to a path in an intelligent way
700 string const addPath(string const & path, string const & path_2)
701 {
702         string buf;
703         string const path2 = os::internal_path(path_2);
704
705         if (!path.empty() && path != "." && path != "./") {
706                 buf = os::internal_path(path);
707                 if (path[path.length() - 1] != '/')
708                         buf += '/';
709         }
710
711         if (!path2.empty()) {
712                 string::size_type const p2start = path2.find_first_not_of('/');
713                 string::size_type const p2end = path2.find_last_not_of('/');
714                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
715                 buf += tmp + '/';
716         }
717         return buf;
718 }
719
720
721 string const changeExtension(string const & oldname, string const & extension)
722 {
723         string::size_type const last_slash = oldname.rfind('/');
724         string::size_type last_dot = oldname.rfind('.');
725         if (last_dot < last_slash && last_slash != string::npos)
726                 last_dot = string::npos;
727
728         string ext;
729         // Make sure the extension starts with a dot
730         if (!extension.empty() && extension[0] != '.')
731                 ext= '.' + extension;
732         else
733                 ext = extension;
734
735         return os::internal_path(oldname.substr(0, last_dot) + ext);
736 }
737
738
739 string const removeExtension(string const & name)
740 {
741         return changeExtension(name, string());
742 }
743
744
745 string const addExtension(string const & name, string const & extension)
746 {
747         if (!extension.empty() && extension[0] != '.')
748                 return name + '.' + extension;
749         return name + extension;
750 }
751
752
753 /// Return the extension of the file (not including the .)
754 string const getExtension(string const & name)
755 {
756         string::size_type const last_slash = name.rfind('/');
757         string::size_type const last_dot = name.rfind('.');
758         if (last_dot != string::npos &&
759             (last_slash == string::npos || last_dot > last_slash))
760                 return name.substr(last_dot + 1,
761                                    name.length() - (last_dot + 1));
762         else
763                 return string();
764 }
765
766
767 string const unzippedFileName(string const & zipped_file)
768 {
769         string const ext = getExtension(zipped_file);
770         if (ext == "gz" || ext == "z" || ext == "Z")
771                 return changeExtension(zipped_file, string());
772         return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
773 }
774
775
776 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
777 {
778         FileName const tempfile = FileName(unzipped_file.empty() ?
779                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
780                 unzipped_file);
781         // Run gunzip
782         string const command = "gunzip -c " +
783                 zipped_file.toFilesystemEncoding() + " > " +
784                 tempfile.toFilesystemEncoding();
785         Systemcall one;
786         one.startscript(Systemcall::Wait, command);
787         // test that command was executed successfully (anon)
788         // yes, please do. (Lgb)
789         return tempfile;
790 }
791
792
793 docstring const makeDisplayPath(string const & path, unsigned int threshold)
794 {
795         string str = path;
796
797         // If file is from LyXDir, display it as if it were relative.
798         string const system = package().system_support().absFileName();
799         if (prefixIs(str, system) && str != system)
800                 return from_utf8("[" + str.erase(0, system.length()) + "]");
801
802         // replace /home/blah with ~/
803         string const home = Package::get_home_dir().absFileName();
804         if (!home.empty() && prefixIs(str, home))
805                 str = subst(str, home, "~");
806
807         if (str.length() <= threshold)
808                 return from_utf8(os::external_path(str));
809
810         string const prefix = ".../";
811         docstring dstr = from_utf8(str);
812         docstring temp;
813
814         while (dstr.length() > threshold)
815                 dstr = split(dstr, temp, '/');
816
817         // Did we shorten everything away?
818         if (dstr.empty()) {
819                 // Yes, filename itself is too long.
820                 // Pick the start and the end of the filename.
821                 dstr = from_utf8(onlyFileName(path));
822                 docstring const head = dstr.substr(0, threshold / 2 - 3);
823
824                 docstring::size_type len = dstr.length();
825                 docstring const tail =
826                         dstr.substr(len - threshold / 2 - 2, len - 1);
827                 dstr = head + from_ascii("...") + tail;
828         }
829
830         return from_utf8(os::external_path(prefix + to_utf8(dstr)));
831 }
832
833
834 #ifdef HAVE_READLINK
835 bool readLink(FileName const & file, FileName & link)
836 {
837         string const encoded = file.toFilesystemEncoding();
838 #ifdef HAVE_DEF_PATH_MAX
839         char linkbuffer[PATH_MAX + 1];
840         int const nRead = ::readlink(encoded.c_str(),
841                                      linkbuffer, sizeof(linkbuffer) - 1);
842         if (nRead <= 0)
843                 return false;
844         linkbuffer[nRead] = '\0'; // terminator
845 #else
846         vector<char> buf(1024);
847         int nRead = -1;
848
849         while (true) {
850                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
851                 if (nRead < 0) {
852                         return false;
853                 }
854                 if (nRead < buf.size() - 1) {
855                         break;
856                 }
857                 buf.resize(buf.size() * 2);
858         }
859         buf[nRead] = '\0'; // terminator
860         const char * linkbuffer = &buf[0];
861 #endif
862         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
863         return true;
864 }
865 #else
866 bool readLink(FileName const &, FileName &)
867 {
868         return false;
869 }
870 #endif
871
872
873 cmd_ret const runCommand(string const & cmd)
874 {
875         // FIXME: replace all calls to RunCommand with ForkedCall
876         // (if the output is not needed) or the code in ISpell.cpp
877         // (if the output is needed).
878
879         // One question is if we should use popen or
880         // create our own popen based on fork, exec, pipe
881         // of course the best would be to have a
882         // pstream (process stream), with the
883         // variants ipstream, opstream
884
885 #if defined (_WIN32)
886         int fno;
887         STARTUPINFO startup;
888         PROCESS_INFORMATION process;
889         SECURITY_ATTRIBUTES security;
890         HANDLE in, out;
891         FILE * inf = 0;
892         bool err2out = false;
893         string command;
894         string const infile = trim(split(cmd, command, '<'), " \"");
895         command = rtrim(command);
896         if (suffixIs(command, "2>&1")) {
897                 command = rtrim(command, "2>&1");
898                 err2out = true;
899         }
900         string const cmdarg = "/d /c " + command;
901         string const comspec = getEnv("COMSPEC");
902
903         security.nLength = sizeof(SECURITY_ATTRIBUTES);
904         security.bInheritHandle = TRUE;
905         security.lpSecurityDescriptor = NULL;
906
907         if (CreatePipe(&in, &out, &security, 0)) {
908                 memset(&startup, 0, sizeof(STARTUPINFO));
909                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
910
911                 startup.cb = sizeof(STARTUPINFO);
912                 startup.dwFlags = STARTF_USESTDHANDLES;
913
914                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
915                 startup.hStdInput = infile.empty()
916                         ? GetStdHandle(STD_INPUT_HANDLE)
917                         : CreateFile(infile.c_str(), GENERIC_READ,
918                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
919                                 FILE_ATTRIBUTE_NORMAL, NULL);
920                 startup.hStdOutput = out;
921
922                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
923                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
924                                 &security, &security, TRUE, CREATE_NO_WINDOW,
925                                 0, 0, &startup, &process)) {
926
927                         CloseHandle(process.hThread);
928                         fno = _open_osfhandle((long)in, _O_RDONLY);
929                         CloseHandle(out);
930                         inf = _fdopen(fno, "r");
931                 }
932         }
933 #elif defined (HAVE_POPEN)
934         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
935 #elif defined (HAVE__POPEN)
936         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
937 #else
938 #error No popen() function.
939 #endif
940
941         // (Claus Hentschel) Check if popen was successful ;-)
942         if (!inf) {
943                 lyxerr << "RunCommand:: could not start child process" << endl;
944                 return make_pair(-1, string());
945         }
946
947         string ret;
948         int c = fgetc(inf);
949         while (c != EOF) {
950                 ret += static_cast<char>(c);
951                 c = fgetc(inf);
952         }
953
954 #if defined (_WIN32)
955         WaitForSingleObject(process.hProcess, INFINITE);
956         if (!infile.empty())
957                 CloseHandle(startup.hStdInput);
958         CloseHandle(process.hProcess);
959         int const pret = fclose(inf);
960 #elif defined (HAVE_PCLOSE)
961         int const pret = pclose(inf);
962 #elif defined (HAVE__PCLOSE)
963         int const pret = _pclose(inf);
964 #else
965 #error No pclose() function.
966 #endif
967
968         if (pret == -1)
969                 perror("RunCommand:: could not terminate child process");
970
971         return make_pair(pret, ret);
972 }
973
974
975 FileName const findtexfile(string const & fil, string const & /*format*/)
976 {
977         /* There is no problem to extend this function too use other
978            methods to look for files. It could be setup to look
979            in environment paths and also if wanted as a last resort
980            to a recursive find. One of the easier extensions would
981            perhaps be to use the LyX file lookup methods. But! I am
982            going to implement this until I see some demand for it.
983            Lgb
984         */
985
986         // If the file can be found directly, we just return a
987         // absolute path version of it.
988         FileName const absfile(makeAbsPath(fil));
989         if (absfile.exists())
990                 return absfile;
991
992         // Now we try to find it using kpsewhich.
993         // It seems from the kpsewhich manual page that it is safe to use
994         // kpsewhich without --format: "When the --format option is not
995         // given, the search path used when looking for a file is inferred
996         // from the name given, by looking for a known extension. If no
997         // known extension is found, the search path for TeX source files
998         // is used."
999         // However, we want to take advantage of the format sine almost all
1000         // the different formats has environment variables that can be used
1001         // to controll which paths to search. f.ex. bib looks in
1002         // BIBINPUTS and TEXBIB. Small list follows:
1003         // bib - BIBINPUTS, TEXBIB
1004         // bst - BSTINPUTS
1005         // graphic/figure - TEXPICTS, TEXINPUTS
1006         // ist - TEXINDEXSTYLE, INDEXSTYLE
1007         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1008         // tex - TEXINPUTS
1009         // tfm - TFMFONTS, TEXFONTS
1010         // This means that to use kpsewhich in the best possible way we
1011         // should help it by setting additional path in the approp. envir.var.
1012         string const kpsecmd = "kpsewhich " + fil;
1013
1014         cmd_ret const c = runCommand(kpsecmd);
1015
1016         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
1017                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
1018         if (c.first != -1)
1019                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
1020         else
1021                 return FileName();
1022 }
1023
1024
1025 int compare_timestamps(FileName const & file1, FileName const & file2)
1026 {
1027         // If the original is newer than the copy, then copy the original
1028         // to the new directory.
1029
1030         int cmp = 0;
1031         if (file1.exists() && file2.exists()) {
1032                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1033                 if (tmp != 0)
1034                         cmp = tmp > 0 ? 1 : -1;
1035
1036         } else if (file1.exists()) {
1037                 cmp = 1;
1038         } else if (file2.exists()) {
1039                 cmp = -1;
1040         }
1041
1042         return cmp;
1043 }
1044
1045
1046 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1047 {
1048         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1049         if (script.empty()) {
1050                 LYXERR0("Could not find bind file conversion "
1051                                 "script prefs2prefs.py.");
1052                 return false;
1053         }
1054
1055         ostringstream command;
1056         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1057           << ' ' << (lfuns ? "-l" : "-p") << ' '
1058                 << quoteName(filename.toFilesystemEncoding())
1059                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1060         string const command_str = command.str();
1061
1062         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1063
1064         cmd_ret const ret = runCommand(command_str);
1065         if (ret.first != 0) {
1066                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1067                 return false;
1068         }
1069         return true;
1070 }
1071
1072 int fileLock(const char * lock_file)
1073 {
1074         int fd = -1;
1075 #if defined(HAVE_LOCKF)
1076         fd = open(lock_file, O_CREAT|O_APPEND|O_SYNC|O_RDWR, 0666);
1077         if (lockf(fd, F_LOCK, 0) != 0) {
1078                 close(fd);
1079                 return(-1);
1080         }
1081 #endif
1082         return(fd);
1083 }
1084
1085 void fileUnlock(int fd, const char * /* lock_file*/)
1086 {
1087 #if defined(HAVE_LOCKF)
1088         if (fd >= 0) {
1089                 if (lockf(fd, F_ULOCK, 0))
1090                         LYXERR0("Can't unlock the file.");
1091                 close(fd);
1092         }
1093 #endif
1094 }
1095
1096 } //namespace support
1097 } // namespace lyx