3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
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
17 * Full author contact details are available in file CREDITS.
19 * General path-mangling functions
24 #include "support/filetools.h"
29 #include "support/convert.h"
30 #include "support/debug.h"
31 #include "support/environment.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/os.h"
35 #include "support/Messages.h"
36 #include "support/Package.h"
37 #include "support/PathChanger.h"
38 #include "support/Systemcall.h"
39 #include "support/qstring_helpers.h"
40 #include "support/TempFile.h"
41 #include "support/textutils.h"
46 #include "support/lassert.h"
67 #include <QCryptographicHash>
81 bool isLyXFileName(string const & filename)
83 return suffixIs(ascii_lowercase(filename), ".lyx");
87 bool isSGMLFileName(string const & filename)
89 return suffixIs(ascii_lowercase(filename), ".sgml");
93 bool isValidLaTeXFileName(string const & filename)
95 string const invalid_chars("#%\"");
96 return filename.find_first_of(invalid_chars) == string::npos;
100 bool isValidDVIFileName(string const & filename)
102 string const invalid_chars("${}()[]^");
103 return filename.find_first_of(invalid_chars) == string::npos;
107 bool isBinaryFile(FileName const & filename)
109 bool isbinary = false;
110 if (filename.empty() || !filename.exists())
114 magic_t magic_cookie = magic_open(MAGIC_MIME_ENCODING);
116 bool detected = true;
117 if (magic_load(magic_cookie, nullptr) != 0) {
118 LYXERR(Debug::FILES, "isBinaryFile: "
119 "Could not load magic database - "
120 << magic_error(magic_cookie));
123 char const *charset = magic_file(magic_cookie,
124 filename.toFilesystemEncoding().c_str());
125 isbinary = contains(charset, "binary");
127 magic_close(magic_cookie);
132 // Try by looking for binary chars at the beginning of the file.
133 // Note that this is formally not correct, since count_bin_chars
134 // expects utf8, and the passed string can be anything: plain text
135 // in any encoding, or really binary data. In practice it works,
136 // since QString::fromUtf8() drops invalid utf8 sequences, and
137 // while the exact number may not be correct, we still get a high
138 // number for truly binary files.
140 ifstream ifs(filename.toFilesystemEncoding().c_str());
144 // Maximum strings to read
145 int const max_count = 50;
147 // Maximum number of binary chars allowed
148 int const max_bin = 5;
153 while (count++ < max_count && !ifs.eof()) {
155 binchars += count_bin_chars(str);
157 return binchars > max_bin;
161 string const latex_path(string const & original_path,
162 latex_path_extension extension,
163 latex_path_dots dots)
165 // On cygwin, we may need windows or posix style paths.
166 string path = os::latex_path(original_path);
167 path = subst(path, "~", "\\string~");
168 if (path.find(' ') != string::npos) {
169 // We can't use '"' because " is sometimes active (e.g. if
170 // babel is loaded with the "german" option)
171 if (extension == EXCLUDE_EXTENSION) {
172 // changeExtension calls os::internal_path internally
173 // so don't use it to remove the extension.
174 string const ext = getExtension(path);
175 string const base = ext.empty() ?
177 path.substr(0, path.length() - ext.length() - 1);
178 // changeExtension calls os::internal_path internally
179 // so don't use it to re-add the extension.
180 path = "\\string\"" + base + "\\string\"." + ext;
182 path = "\\string\"" + path + "\\string\"";
186 if (dots != ESCAPE_DOTS)
189 // Replace dots with the lyxdot macro, but only in the file name,
190 // not the directory part.
191 // addName etc call os::internal_path internally
192 // so don't use them for path manipulation
193 // The directory separator is always '/' for LaTeX.
194 string::size_type pos = path.rfind('/');
195 if (pos == string::npos)
196 return subst(path, ".", "\\lyxdot ");
197 return path.substr(0, pos) + subst(path.substr(pos), ".", "\\lyxdot ");
201 // Substitutes spaces with underscores in filename (and path)
202 FileName const makeLatexName(FileName const & file)
204 string name = file.onlyFileName();
205 string const path = file.onlyPath().absFileName() + "/";
207 // ok so we scan through the string twice, but who cares.
208 // FIXME: in Unicode time this will break for sure! There is
209 // a non-latin world out there...
210 string const keep = "abcdefghijklmnopqrstuvwxyz"
211 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
212 "@!'()*+,-./0123456789:;<=>?[]`|";
214 string::size_type pos = 0;
215 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
218 FileName latex_name(path + name);
219 latex_name.changeExtension(".tex");
224 string const quoteName(string const & name, quote_style style)
228 // This does not work on native Windows for filenames
229 // containing the following characters < > : " / \ | ? *
230 // Moreover, it can't be made to work, as, according to
231 // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
232 // those are reserved characters, and thus are forbidden.
233 // Please, also note that the command-line parser in
234 // ForkedCall::generateChild cannot deal with filenames
235 // containing " or ', therefore we don't pass user filenames
236 // to child processes if possible. We store them in a python
237 // script instead, where we don't have these limitations.
239 return (os::shell() == os::UNIX) ?
240 '\'' + subst(name, "'", "\'\\\'\'") + '\'' :
243 // According to the QProcess parser, a single double
244 // quote is represented by three consecutive ones.
245 // Here we simply escape the double quote and let our
246 // simple parser in Systemcall.cpp do the substitution.
247 return '"' + subst(name, "\"", "\\\"") + '"';
249 case quote_shell_filename:
250 return quoteName(os::external_path(name), quote_shell);
252 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
255 // shut up stupid compiler
261 // Uses a string of paths separated by ";"s to find a file to open.
262 // Can't cope with pathnames with a ';' in them. Returns full path to file.
263 // If path entry begins with $$LyX/, use system_lyxdir
264 // If path entry begins with $$User/, use user_lyxdir
265 // Example: "$$User/doc;$$LyX/doc"
266 FileName const fileOpenSearch(string const & path, string const & name,
271 bool notfound = true;
272 string tmppath = split(path, path_element, ';');
274 while (notfound && !path_element.empty()) {
275 path_element = os::internal_path(path_element);
276 if (!suffixIs(path_element, '/'))
278 path_element = subst(path_element, "$$LyX",
279 package().system_support().absFileName());
280 path_element = subst(path_element, "$$User",
281 package().user_support().absFileName());
283 real_file = fileSearch(path_element, name, ext);
285 if (real_file.empty()) {
287 tmppath = split(tmppath, path_element, ';');
288 } while (!tmppath.empty() && path_element.empty());
298 // Returns the real name of file name in directory path, with optional
300 FileName const fileSearch(string const & path, string const & name,
301 string const & exts, search_mode mode)
303 // if `name' is an absolute path, we ignore the setting of `path'
304 // Expand Environmentvariables in 'name'
305 string const tmpname = replaceEnvironmentPath(name);
306 FileName fullname(makeAbsPath(tmpname, path));
307 // search first without extension, then with it.
308 if (fullname.isReadableFile())
312 return mode == may_not_exist ? fullname : FileName();
314 string ext = token(exts, ',', n);
315 while (!ext.empty()) {
316 // Only add the extension if it is not already the extension of
318 bool addext = getExtension(fullname.absFileName()) != ext;
320 if (mode == check_hidpi) {
321 FileName fullname2x = FileName(addExtension(fullname.absFileName() + "@2x", ext));
322 if (fullname2x.isReadableFile())
325 fullname = FileName(addExtension(fullname.absFileName(), ext));
327 if (fullname.isReadableFile() || mode == may_not_exist)
330 fullname.changeExtension("");
331 ext = token(exts, ',', ++n);
337 // Search the file name.ext in the subdirectory dir of
339 // 2) build_lyxdir (if not empty)
341 FileName const libFileSearch(string const & dir, string const & name,
342 string const & ext, search_mode mode,
343 bool const only_global)
347 fullname = fileSearch(addPath(package().user_support().absFileName(), dir),
349 if (!fullname.empty())
353 if (!package().build_support().empty())
354 fullname = fileSearch(addPath(package().build_support().absFileName(), dir),
356 if (!fullname.empty())
359 return fileSearch(addPath(package().system_support().absFileName(), dir),
364 FileName const i18nLibFileSearch(string const & dir, string const & name,
367 // if the LANGUAGE variable is set, use it as a fallback for searching for files.
368 string lang = getGuiMessages().language();
369 string const language = getEnv("LANGUAGE");
370 if (!language.empty())
371 lang += ":" + language;
373 for (auto const & l : getVectorFromString(lang, ":")) {
375 // First try with the full name
376 // `en' files are not in a subdirectory
378 tmp = libFileSearch(dir, name, ext);
380 tmp = libFileSearch(addPath(dir, l), name, ext);
384 // Then the name without country code
385 string const shortl = token(l, '_', 0);
387 tmp = libFileSearch(addPath(dir, shortl), name, ext);
393 return libFileSearch(dir, name, ext);
397 FileName const imageLibFileSearch(string & dir, string const & name,
398 string const & ext, search_mode mode)
400 if (!lyx::lyxrc.icon_set.empty()) {
401 string const imagedir = addPath(dir, lyx::lyxrc.icon_set);
402 FileName const fn = libFileSearch(imagedir, name, ext, mode);
408 return libFileSearch(dir, name, ext, mode);
412 string const commandPrep(string const & command_in)
414 static string const token_scriptpath = "$$s/";
415 string const python_call = os::python();
417 string command = command_in;
418 if (prefixIs(command_in, python_call))
419 command = os::python() + command_in.substr(python_call.length());
421 // Find the starting position of "$$s/"
422 string::size_type const pos1 = command.find(token_scriptpath);
423 if (pos1 == string::npos)
425 // Find the end of the "$$s/some_subdir/some_script" word within
426 // command. Assumes that the script name does not contain spaces.
427 string::size_type const start_script = pos1 + 4;
428 string::size_type const pos2 = command.find(' ', start_script);
429 string::size_type const size_script = pos2 == string::npos?
430 (command.size() - start_script) : pos2 - start_script;
432 // Does this script file exist?
433 string const script =
434 libFileSearch(".", command.substr(start_script, size_script)).absFileName();
436 if (script.empty()) {
437 // Replace "$$s/" with ""
438 command.erase(pos1, 4);
440 quote_style style = quote_shell;
441 if (prefixIs(command, os::python()))
442 style = quote_python;
444 // Replace "$$s/foo/some_script" with "<path to>/some_script".
445 string::size_type const size_replace = size_script + 4;
446 command.replace(pos1, size_replace, quoteName(script, style));
453 FileName const tempFileName(FileName const & tempdir, string const & mask, bool const dir)
455 return tempFileName(TempFile(tempdir, mask).name(), dir);
459 FileName const tempFileName(string const & mask, bool const dir)
461 return tempFileName(TempFile(mask).name(), dir);
465 FileName const tempFileName(FileName tempfile, bool const dir)
467 // Since the QTemporaryFile object is destroyed at function return
468 // (which is what is intended here), the next call to this function
469 // may return the same file name again.
470 // Thus, in order to prevent race conditions, we track returned names
471 // and create our own unique names if QTemporaryFile returns a name again.
472 if (tmp_names_.find(tempfile.absFileName()) == tmp_names_.end()) {
473 tmp_names_.insert(tempfile.absFileName());
477 // OK, we need another name. Simply append digits.
478 FileName tmp = tempfile;
481 // Store and remove extensions
482 ext = "." + tempfile.extension();
483 tmp.changeExtension("");
485 for (int i = 1; i < INT_MAX ;++i) {
486 // Append digit to filename and re-add extension
487 string const new_fn =
488 tmp.absFileName() + convert<string>(i) + ext;
489 if (tmp_names_.find(new_fn) == tmp_names_.end()) {
490 tmp_names_.insert(new_fn);
491 tempfile.set(new_fn);
496 // This should not happen!
497 LYXERR0("tempFileName(): Could not create unique temp file name!");
502 void removeTempFile(FileName const & fn)
507 string const abs = fn.absFileName();
508 tmp_names_.erase(abs);
513 static FileName createTmpDir(FileName const & tempdir, string const & mask)
515 LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
516 << "createTmpDir: mask=`" << mask << '\'');
518 QFileInfo tmp_fi(QDir(toqstr(tempdir.absFileName())), toqstr(mask));
519 FileName const tmpfl =
520 tempFileName(FileName(fromqstr(tmp_fi.absolutePath())),
521 fromqstr(tmp_fi.fileName()) + ".XXXXXXXXXXXX", true);
523 if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
524 LYXERR0("LyX could not create temporary directory in " << tempdir
533 FileName const createLyXTmpDir(FileName const & deflt)
535 if (deflt.empty() || deflt == package().system_temp_dir())
536 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
538 if (deflt.createDirectory(0777))
541 if (deflt.isDirWritable()) {
542 // deflt could not be created because it
543 // did exist already, so let's create our own
545 return createTmpDir(deflt, "lyx_tmpdir");
547 // some other error occurred.
548 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
553 // Strip filename from path name
554 string const onlyPath(string const & filename)
556 // If empty filename, return empty
557 if (filename.empty())
560 // Find last / or start of filename
561 size_t j = filename.rfind('/');
562 return j == string::npos ? "./" : filename.substr(0, j + 1);
566 // Convert relative path into absolute path based on a basepath.
567 // If relpath is absolute, just use that.
568 // If basepath is empty, use CWD as base.
569 // Note that basePath can be a relative path, in the sense that it may
570 // not begin with "/" (e.g.), but it should NOT contain such constructs
572 // FIXME It might be nice if the code didn't simply assume that.
573 FileName const makeAbsPath(string const & relPath, string const & basePath)
575 // checks for already absolute path
576 if (FileName::isAbsolute(relPath))
577 return FileName(relPath);
579 // Copies given paths
580 string tempRel = os::internal_path(relPath);
581 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
582 tempRel = subst(tempRel, "//", "/");
586 if (FileName::isAbsolute(basePath))
589 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
591 // Handle /./ at the end of the path
592 while (suffixIs(tempBase, "/./"))
593 tempBase.erase(tempBase.length() - 2);
595 // processes relative path
596 string rTemp = tempRel;
599 // Check for a leading "~"
601 rTemp = split(rTemp, temp, '/');
603 tempBase = Package::get_home_dir().absFileName();
608 while (!rTemp.empty()) {
610 rTemp = split(rTemp, temp, '/');
612 if (temp == ".") continue;
614 // Remove one level of TempBase
615 if (tempBase.length() <= 1) {
616 //this is supposed to be an absolute path, so...
620 //erase a trailing slash if there is one
621 if (suffixIs(tempBase, "/"))
622 tempBase.erase(tempBase.length() - 1, string::npos);
624 string::size_type i = tempBase.length() - 1;
625 while (i > 0 && tempBase[i] != '/')
628 tempBase.erase(i, string::npos);
631 } else if (temp.empty() && !rTemp.empty()) {
632 tempBase = os::current_root() + rTemp;
635 // Add this piece to TempBase
636 if (!suffixIs(tempBase, '/'))
642 // returns absolute path
643 return FileName(tempBase);
647 // Correctly append filename to the pathname.
648 // If pathname is '.', then don't use pathname.
649 // Chops any path of filename.
650 string const addName(string const & path, string const & fname)
652 string const basename = onlyFileName(fname);
655 if (path != "." && path != "./" && !path.empty()) {
656 buf = os::internal_path(path);
657 if (!suffixIs(buf, '/'))
661 return buf + basename;
665 string const addPathName(std::string const & path, std::string const & fname)
667 string const pathpart = onlyPath(fname);
668 string const namepart = onlyFileName(fname);
669 string newpath = path;
670 if (!pathpart.empty())
671 newpath = addPath(newpath, pathpart);
672 if (!namepart.empty())
673 newpath = addName(newpath, namepart);
678 // Strips path from filename
679 string const onlyFileName(string const & fname)
684 string::size_type j = fname.rfind('/');
685 if (j == string::npos) // no '/' in fname
689 return fname.substr(j + 1);
693 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
694 // If VAR does not exist, ${VAR} and $VAR are left as is in the string.
695 string const replaceEnvironmentPath(string const & path)
697 if (!contains(path, '$'))
700 // ${VAR} is defined as
701 // $\{[A-Za-z_][A-Za-z_0-9]*\}
702 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
704 // $VAR is defined as:
705 // $[A-Za-z_][A-Za-z_0-9]*
706 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
708 // Coverity thinks that the regex constructor can return an
709 // exception. We know that it is not true since our regex are
710 // hardcoded, but we have to protect against that nevertheless.
712 static regex const envvar_br_re("(.*)" + envvar_br + "(.*)");
713 static regex const envvar_re("(.*)" + envvar + "(.*)");
714 string result = path;
717 bool brackets = true;
718 if (!regex_match(result, what, envvar_br_re)) {
720 if (!regex_match(result, what, envvar_re))
723 string env_var = getEnv(what.str(2));
724 if (env_var.empty()) {
725 // temporarily use alert/bell (0x07) in place of $
727 env_var = "\a{" + what.str(2) + '}';
729 env_var = "\a" + what.str(2);
731 result = what.str(1) + env_var + what.str(3);
733 return subst(result, '\a', '$');
734 } catch (exception const & e) {
735 LYXERR0("Something is very wrong: " << e.what());
741 // Return a command prefix for setting the environment of the TeX engine.
742 string latexEnvCmdPrefix(string const & path, string const & lpath)
744 bool use_lpath = !(lpath.empty() || lpath == "." || lpath == "./");
746 if (path.empty() || (lyxrc.texinputs_prefix.empty() && !use_lpath))
749 string texinputs_prefix = lyxrc.texinputs_prefix.empty() ? string()
750 : os::latex_path_list(
751 replaceCurdirPath(path, lyxrc.texinputs_prefix));
752 string const allother_prefix = os::latex_path_list(path);
753 string const sep = string(1, os::path_separator(os::TEXENGINE));
754 string const texinputs = getEnv("TEXINPUTS");
755 string const bibinputs = getEnv("BIBINPUTS");
756 string const bstinputs = getEnv("BSTINPUTS");
757 string const texfonts = getEnv("TEXFONTS");
760 string const abslpath = FileName::isAbsolute(lpath)
761 ? os::latex_path(lpath)
762 : os::latex_path(FileName(path + "/" + lpath).realPath());
763 if (texinputs_prefix.empty())
764 texinputs_prefix = abslpath;
765 else if (suffixIs(texinputs_prefix, sep))
766 texinputs_prefix.append(abslpath + sep);
768 texinputs_prefix.append(sep + abslpath);
771 if (os::shell() == os::UNIX)
772 return "env TEXINPUTS=\"." + sep + texinputs_prefix
773 + sep + texinputs + "\" "
774 + "BIBINPUTS=\"." + sep + allother_prefix
775 + sep + bibinputs + "\" "
776 + "BSTINPUTS=\"." + sep + allother_prefix
777 + sep + bstinputs + "\" "
778 + "TEXFONTS=\"." + sep + allother_prefix
779 + sep + texfonts + "\" ";
781 // NOTE: the dummy blank dirs are necessary to force the
782 // QProcess parser to quote the argument (see bug 9453)
783 return "cmd /d /c set \"TEXINPUTS=." + sep + " "
784 + sep + texinputs_prefix
785 + sep + texinputs + "\" & "
786 + "set \"BIBINPUTS=." + sep + " "
787 + sep + allother_prefix
788 + sep + bibinputs + "\" & "
789 + "set \"BSTINPUTS=." + sep + " "
790 + sep + allother_prefix
791 + sep + bstinputs + "\" & "
792 + "set \"TEXFONTS=." + sep + " "
793 + sep + allother_prefix
794 + sep + texfonts + "\" & ";
798 // Replace current directory in all elements of a path list with a given path.
799 string const replaceCurdirPath(string const & path, string const & pathlist)
801 string const oldpathlist = replaceEnvironmentPath(pathlist);
802 char const sep = os::path_separator();
805 for (size_t i = 0, k = 0; i != string::npos; k = i) {
806 i = oldpathlist.find(sep, i);
807 string p = oldpathlist.substr(k, i - k);
808 if (FileName::isAbsolute(p)) {
814 } else if (prefixIs(p, "./")) {
816 while (p[offset] == '/')
819 newpathlist += addPath(path, p.substr(offset));
820 if (suffixIs(p, "//"))
823 if (i != string::npos) {
825 // Stop here if the last element is empty
826 if (++i == oldpathlist.length())
834 // Make relative path out of two absolute paths
835 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
836 // Makes relative path out of absolute path. If it is deeper than basepath,
837 // it's easy. If basepath and abspath share something (they are all deeper
838 // than some directory), it'll be rendered using ..'s. If they are completely
839 // different, then the absolute path will be used as relative path.
841 docstring::size_type const abslen = abspath.length();
842 docstring::size_type const baselen = basepath.length();
844 docstring::size_type i = os::common_path(abspath, basepath);
847 // actually no match - cannot make it relative
851 // Count how many dirs there are in basepath above match
852 // and append as many '..''s into relpath
854 docstring::size_type j = i;
855 while (j < baselen) {
856 if (basepath[j] == '/') {
857 if (j + 1 == baselen)
864 // Append relative stuff from common directory to abspath
865 if (abspath[i] == '/')
867 for (; i < abslen; ++i)
870 if (suffixIs(buf, '/'))
871 buf.erase(buf.length() - 1);
872 // Substitute empty with .
879 // Append sub-directory(ies) to a path in an intelligent way
880 string const addPath(string const & path, string const & path_2)
883 string const path2 = os::internal_path(path_2);
885 if (!path.empty() && path != "." && path != "./") {
886 buf = os::internal_path(path);
887 if (path[path.length() - 1] != '/')
891 if (!path2.empty()) {
892 string::size_type const p2start = path2.find_first_not_of('/');
893 string::size_type const p2end = path2.find_last_not_of('/');
894 string const tmp = path2.substr(p2start, p2end - p2start + 1);
901 string const changeExtension(string const & oldname, string const & extension)
903 string::size_type const last_slash = oldname.rfind('/');
904 string::size_type last_dot = oldname.rfind('.');
905 if (last_dot < last_slash && last_slash != string::npos)
906 last_dot = string::npos;
909 // Make sure the extension starts with a dot
910 if (!extension.empty() && extension[0] != '.')
911 ext= '.' + extension;
915 return os::internal_path(oldname.substr(0, last_dot) + ext);
919 string const removeExtension(string const & name)
921 return changeExtension(name, string());
925 string const addExtension(string const & name, string const & extension)
927 if (!extension.empty() && extension[0] != '.')
928 return name + '.' + extension;
929 return name + extension;
933 /// Return the extension of the file (not including the .)
934 string const getExtension(string const & name)
936 string::size_type const last_slash = name.rfind('/');
937 string::size_type const last_dot = name.rfind('.');
938 if (last_dot != string::npos &&
939 (last_slash == string::npos || last_dot > last_slash))
940 return name.substr(last_dot + 1,
941 name.length() - (last_dot + 1));
947 docstring const provideScheme(docstring const & name, docstring const & scheme)
949 if (prefixIs(name, scheme + "://"))
951 QUrl url(toqstr(name));
952 if (!url.scheme().isEmpty())
953 // Has a scheme. Return as is.
955 if (scheme == from_ascii("doi")) {
956 // check if it is the pure DOI (without URL)
957 if (isDigitASCII(name[1]))
958 return from_ascii("https://doi.org/") + name;
960 url.setScheme(toqstr(scheme));
961 return qstring_to_ucs4(url.toString());
965 string const unzippedFileName(string const & zipped_file)
967 string const ext = getExtension(zipped_file);
968 if (ext == "gz" || ext == "z" || ext == "Z")
969 return changeExtension(zipped_file, string());
970 else if (ext == "svgz")
971 return changeExtension(zipped_file, "svg");
972 return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
976 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
978 FileName const tempfile = FileName(unzipped_file.empty() ?
979 unzippedFileName(zipped_file.toFilesystemEncoding()) :
982 string const command = "gunzip -c \"" +
983 zipped_file.toFilesystemEncoding() + "\" > \"" +
984 tempfile.toFilesystemEncoding() + "\"";
986 one.startscript(Systemcall::Wait, command);
987 // test that command was executed successfully (anon)
988 // yes, please do. (Lgb)
993 docstring const makeDisplayPath(string const & path, unsigned int threshold)
997 // Recode URL encoded chars.
998 str = from_percent_encoding(str);
1000 // If file is from LyXDir, display it as if it were relative.
1001 string const system = package().system_support().absFileName();
1002 if (prefixIs(str, system) && str != system)
1003 return from_utf8("[" + str.erase(0, system.length()) + "]");
1005 // replace /home/blah with ~/
1006 string const home = Package::get_home_dir().absFileName();
1007 if (!home.empty() && prefixIs(str, home))
1008 str = subst(str, home, "~");
1010 if (str.length() <= threshold)
1011 return from_utf8(os::external_path(str));
1013 string const prefix = ".../";
1014 docstring dstr = from_utf8(str);
1017 while (dstr.length() > threshold)
1018 dstr = split(dstr, temp, '/');
1020 // Did we shorten everything away?
1022 // Yes, filename itself is too long.
1023 // Pick the start and the end of the filename.
1024 docstring fstr = from_utf8(onlyFileName(path));
1026 if (support::truncateWithEllipsis(dstr, threshold / 2))
1027 dstr += fstr.substr(fstr.length() - threshold / 2 - 2,
1031 return from_utf8(os::external_path(prefix + to_utf8(dstr)));
1035 #ifdef HAVE_READLINK
1036 bool readLink(FileName const & file, FileName & link)
1038 string const encoded = file.toFilesystemEncoding();
1039 #ifdef HAVE_DEF_PATH_MAX
1040 char linkbuffer[PATH_MAX + 1];
1041 ssize_t const nRead = ::readlink(encoded.c_str(),
1042 linkbuffer, sizeof(linkbuffer) - 1);
1045 linkbuffer[nRead] = '\0'; // terminator
1047 vector<char> buf(1024);
1051 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
1055 if (static_cast<size_t>(nRead) < buf.size() - 1) {
1058 buf.resize(buf.size() * 2);
1060 buf[nRead] = '\0'; // terminator
1061 const char * linkbuffer = &buf[0];
1063 link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
1067 bool readLink(FileName const &, FileName &)
1074 cmd_ret const runCommand(string const & cmd)
1076 // FIXME: replace all calls to RunCommand with ForkedCall
1077 // (if the output is not needed) or the code in ISpell.cpp
1078 // (if the output is needed).
1080 // One question is if we should use popen or
1081 // create our own popen based on fork, exec, pipe
1082 // of course the best would be to have a
1083 // pstream (process stream), with the
1084 // variants ipstream, opstream
1087 lyxerr << "\nRunning: " << cmd << endl;
1089 LYXERR(Debug::INFO,"Running: " << cmd);
1091 #if defined (_WIN32)
1092 STARTUPINFO startup;
1093 PROCESS_INFORMATION process;
1094 SECURITY_ATTRIBUTES security;
1097 bool err2out = false;
1099 string const infile = trim(split(cmd, command, '<'), " \"");
1100 command = rtrim(command);
1101 if (suffixIs(command, "2>&1")) {
1102 command = rtrim(command, "2>&1");
1105 string const cmdarg = "/d /c \"" + command + "\"";
1106 string const comspec = getEnv("COMSPEC");
1108 security.nLength = sizeof(SECURITY_ATTRIBUTES);
1109 security.bInheritHandle = TRUE;
1110 security.lpSecurityDescriptor = NULL;
1112 if (CreatePipe(&in, &out, &security, 0)) {
1113 memset(&startup, 0, sizeof(STARTUPINFO));
1114 memset(&process, 0, sizeof(PROCESS_INFORMATION));
1116 startup.cb = sizeof(STARTUPINFO);
1117 startup.dwFlags = STARTF_USESTDHANDLES;
1119 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
1120 startup.hStdInput = infile.empty()
1121 ? GetStdHandle(STD_INPUT_HANDLE)
1122 : CreateFile(infile.c_str(), GENERIC_READ,
1123 FILE_SHARE_READ, &security, OPEN_EXISTING,
1124 FILE_ATTRIBUTE_NORMAL, NULL);
1125 startup.hStdOutput = out;
1127 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
1128 CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
1129 &security, &security, TRUE, CREATE_NO_WINDOW,
1130 0, 0, &startup, &process)) {
1132 CloseHandle(process.hThread);
1133 int fno = _open_osfhandle((intptr_t)in, _O_RDONLY);
1135 inf = _fdopen(fno, "r");
1138 #elif defined (HAVE_POPEN)
1139 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1140 #elif defined (HAVE__POPEN)
1141 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1143 #error No popen() function.
1146 // (Claus Hentschel) Check if popen was successful ;-)
1148 lyxerr << "RunCommand: could not start child process" << endl;
1149 return { false, string() };
1155 result += static_cast<char>(c);
1159 #if defined (_WIN32)
1160 WaitForSingleObject(process.hProcess, INFINITE);
1162 BOOL success = GetExitCodeProcess(process.hProcess, &pret);
1163 bool valid = (pret == 0) && success;
1166 if (!infile.empty())
1167 CloseHandle(startup.hStdInput);
1168 CloseHandle(process.hProcess);
1169 if (fclose(inf) != 0)
1171 #elif defined (HAVE_PCLOSE)
1172 int const pret = pclose(inf);
1173 bool const valid = (WEXITSTATUS(pret) == 0);
1174 #elif defined (HAVE__PCLOSE)
1175 int const pret = _pclose(inf);
1176 bool const valid = (WEXITSTATUS(pret) == 0);
1178 #error No pclose() function.
1182 perror("RunCommand: could not terminate child process");
1184 return { valid, result };
1188 FileName const findtexfile(string const & fil, string const & /*format*/,
1189 bool const onlykpse)
1191 /* There is no problem to extend this function to use other
1192 methods to look for files. It could be setup to look
1193 in environment paths and also if wanted as a last resort
1194 to a recursive find. One of the easier extensions would
1195 perhaps be to use the LyX file lookup methods. But! I am
1196 going to implement this until I see some demand for it.
1200 // If the file can be found directly, we just return a
1201 // absolute path version of it.
1203 FileName const absfile(makeAbsPath(fil));
1204 if (absfile.exists())
1208 // Now we try to find it using kpsewhich.
1209 // It seems from the kpsewhich manual page that it is safe to use
1210 // kpsewhich without --format: "When the --format option is not
1211 // given, the search path used when looking for a file is inferred
1212 // from the name given, by looking for a known extension. If no
1213 // known extension is found, the search path for TeX source files
1215 // However, we want to take advantage of the format sine almost all
1216 // the different formats has environment variables that can be used
1217 // to control which paths to search. f.ex. bib looks in
1218 // BIBINPUTS and TEXBIB. Small list follows:
1219 // bib - BIBINPUTS, TEXBIB
1221 // graphic/figure - TEXPICTS, TEXINPUTS
1222 // ist - TEXINDEXSTYLE, INDEXSTYLE
1223 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1225 // tfm - TFMFONTS, TEXFONTS
1226 // This means that to use kpsewhich in the best possible way we
1227 // should help it by setting additional path in the approp. envir.var.
1228 string const kpsecmd = "kpsewhich " + fil;
1230 cmd_ret const c = runCommand(kpsecmd);
1232 LYXERR(Debug::OUTFILE, "kpse status = " << c.valid << '\n'
1233 << "kpse result = `" << rtrim(c.result, "\n\r") << '\'');
1235 return FileName(rtrim(to_utf8(from_filesystem8bit(c.result)), "\n\r"));
1241 int compare_timestamps(FileName const & file1, FileName const & file2)
1243 // If the original is newer than the copy, then copy the original
1244 // to the new directory.
1247 if (file1.exists() && file2.exists()) {
1248 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1250 cmp = tmp > 0 ? 1 : -1;
1252 } else if (file1.exists()) {
1254 } else if (file2.exists()) {
1262 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1264 FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1265 if (script.empty()) {
1266 LYXERR0("Could not find bind file conversion "
1267 "script prefs2prefs.py.");
1271 ostringstream command;
1272 command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1273 << ' ' << (lfuns ? "-l" : "-p") << ' '
1274 << quoteName(filename.toFilesystemEncoding())
1275 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1276 string const command_str = command.str();
1278 LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1280 cmd_ret const ret = runCommand(command_str);
1282 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1289 bool configFileNeedsUpdate(string const & file)
1291 // We cannot initialize configure_script directly because the package
1292 // is not initialized yet when static objects are constructed.
1293 static FileName configure_script;
1294 static bool firstrun = true;
1297 FileName(addName(package().system_support().absFileName(),
1303 FileName(addName(package().user_support().absFileName(), file));
1304 return !absfile.exists()
1305 || configure_script.lastModified() > absfile.lastModified();
1309 int fileLock(const char * lock_file)
1312 #if defined(HAVE_LOCKF)
1313 fd = open(lock_file, O_CREAT|O_APPEND|O_SYNC|O_RDWR, 0666);
1316 if (lockf(fd, F_LOCK, 0) != 0) {
1325 void fileUnlock(int fd, const char * /* lock_file*/)
1327 #if defined(HAVE_LOCKF)
1329 if (lockf(fd, F_ULOCK, 0))
1330 LYXERR0("Can't unlock the file.");
1337 std::string toHexHash(const std::string & str, bool shorten)
1339 auto hashAlgo = QCryptographicHash::Sha256;
1341 QByteArray hash = QCryptographicHash::hash(toqstr(str).toLocal8Bit(), hashAlgo);
1342 QString qshash=QString(hash.toHex());
1344 /* For shortened case we take 12 leftmost chars (6 bytes encoded).
1345 * Random experiment shows:
1346 * 8 chars: 16 collisions for 10^5 graphic filenames
1347 * 12 chars: 0 collisions for 10^5 graphic filenames
1350 qshash=qshash.left(12);
1352 return fromqstr(qshash);
1356 std::string sanitizeFileName(const std::string & str)
1358 // The list of characters to keep is probably over-restrictive,
1359 // but it is not really a problem.
1360 // Apart from non-ASCII characters, at least the following characters
1361 // are forbidden: '/', '.', ' ', and ':'.
1362 // On windows it is not possible to create files with '<', '>' or '?'
1364 static std::string const keep = "abcdefghijklmnopqrstuvwxyz"
1365 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1368 std::string name = str;
1369 string::size_type pos = 0;
1370 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
1376 } // namespace support