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