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