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/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/Package.h"
32 #include "support/Path.h"
33 #include "support/Systemcall.h"
35 // FIXME Interface violation
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
56 # include <sys/types.h>
57 # include <sys/stat.h>
71 #define WRITEBUFFERSIZE (16384)
72 #define MAXFILENAME (256)
75 #ifndef CXX_GLOBAL_CSTD
84 using std::ostringstream;
88 namespace fs = boost::filesystem;
93 bool isLyXFilename(string const & filename)
95 return suffixIs(ascii_lowercase(filename), ".lyx");
99 bool isSGMLFilename(string const & filename)
101 return suffixIs(ascii_lowercase(filename), ".sgml");
105 bool isValidLaTeXFilename(string const & filename)
107 string const invalid_chars("#$%{}()[]\"^");
108 if (filename.find_first_of(invalid_chars) != string::npos)
115 string const latex_path(string const & original_path,
116 latex_path_extension extension,
117 latex_path_dots dots)
119 // On cygwin, we may need windows or posix style paths.
120 string path = os::latex_path(original_path);
121 path = subst(path, "~", "\\string~");
122 if (path.find(' ') != string::npos) {
123 // We can't use '"' because " is sometimes active (e.g. if
124 // babel is loaded with the "german" option)
125 if (extension == EXCLUDE_EXTENSION) {
126 // ChangeExtension calls os::internal_path internally
127 // so don't use it to remove the extension.
128 string const ext = getExtension(path);
129 string const base = ext.empty() ?
131 path.substr(0, path.length() - ext.length() - 1);
132 // ChangeExtension calls os::internal_path internally
133 // so don't use it to re-add the extension.
134 path = "\\string\"" + base + "\\string\"." + ext;
136 path = "\\string\"" + path + "\\string\"";
140 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
144 // Substitutes spaces with underscores in filename (and path)
145 string const makeLatexName(string const & file)
147 string name = onlyFilename(file);
148 string const path = onlyPath(file);
150 // ok so we scan through the string twice, but who cares.
151 string const keep = "abcdefghijklmnopqrstuvwxyz"
152 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
153 "@!'()*+,-./0123456789:;<=>?[]`|";
155 string::size_type pos = 0;
156 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
159 return addName(path, name);
163 string const quoteName(string const & name, quote_style style)
167 // This does not work for filenames containing " (windows)
168 // or ' (all other OSes). This can't be changed easily, since
169 // we would need to adapt the command line parser in
170 // Forkedcall::generateChild. Therefore we don't pass user
171 // filenames to child processes if possible. We store them in
172 // a python script instead, where we don't have these
174 return (os::shell() == os::UNIX) ?
178 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
181 // shut up stupid compiler
186 bool isFileReadable(FileName const & filename)
188 std::string const path = filename.toFilesystemEncoding();
189 return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
193 //returns true: dir writeable
194 // false: not writeable
195 bool isDirWriteable(FileName const & path)
197 LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
199 FileName const tmpfl(tempName(path, "lyxwritetest"));
210 // Uses a string of paths separated by ";"s to find a file to open.
211 // Can't cope with pathnames with a ';' in them. Returns full path to file.
212 // If path entry begins with $$LyX/, use system_lyxdir
213 // If path entry begins with $$User/, use user_lyxdir
214 // Example: "$$User/doc;$$LyX/doc"
215 FileName const fileOpenSearch(string const & path, string const & name,
220 bool notfound = true;
221 string tmppath = split(path, path_element, ';');
223 while (notfound && !path_element.empty()) {
224 path_element = os::internal_path(path_element);
225 if (!suffixIs(path_element, '/'))
227 path_element = subst(path_element, "$$LyX",
228 package().system_support().absFilename());
229 path_element = subst(path_element, "$$User",
230 package().user_support().absFilename());
232 real_file = fileSearch(path_element, name, ext);
234 if (real_file.empty()) {
236 tmppath = split(tmppath, path_element, ';');
237 } while (!tmppath.empty() && path_element.empty());
247 /// Returns a vector of all files in directory dir having extension ext.
248 vector<FileName> const dirList(FileName const & dir, string const & ext)
250 // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
251 vector<FileName> dirlist;
253 string const encoded_dir = dir.toFilesystemEncoding();
254 if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
256 << "Directory \"" << dir
257 << "\" does not exist to DirList." << endl;
262 if (!ext.empty() && ext[0] != '.')
266 fs::directory_iterator dit(encoded_dir);
267 fs::directory_iterator end;
268 for (; dit != end; ++dit) {
269 string const & fil = dit->leaf();
270 if (suffixIs(fil, extension))
271 dirlist.push_back(FileName::fromFilesystemEncoding(
272 encoded_dir + '/' + fil));
278 // Returns the real name of file name in directory path, with optional
280 FileName const fileSearch(string const & path, string const & name,
281 string const & ext, search_mode mode)
283 // if `name' is an absolute path, we ignore the setting of `path'
284 // Expand Environmentvariables in 'name'
285 string const tmpname = replaceEnvironmentPath(name);
286 FileName fullname(makeAbsPath(tmpname, path));
287 // search first without extension, then with it.
288 if (isFileReadable(fullname))
292 return mode == allow_unreadable ? fullname : FileName();
293 // Only add the extension if it is not already the extension of
295 if (getExtension(fullname.absFilename()) != ext)
296 fullname = FileName(addExtension(fullname.absFilename(), ext));
297 if (isFileReadable(fullname) || mode == allow_unreadable)
303 // Search the file name.ext in the subdirectory dir of
305 // 2) build_lyxdir (if not empty)
307 FileName const libFileSearch(string const & dir, string const & name,
310 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
312 if (!fullname.empty())
315 if (!package().build_support().empty())
316 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
318 if (!fullname.empty())
321 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
325 FileName const i18nLibFileSearch(string const & dir, string const & name,
328 /* The highest priority value is the `LANGUAGE' environment
329 variable. But we don't use the value if the currently
330 selected locale is the C locale. This is a GNU extension.
332 Otherwise, w use a trick to guess what gettext has done:
333 each po file is able to tell us its name. (JMarc)
336 string lang = to_ascii(_("[[Replace with the code of your language]]"));
337 string const language = getEnv("LANGUAGE");
338 if (!lang.empty() && !language.empty())
342 lang = split(lang, l, ':');
345 // First try with the full name
346 tmp = libFileSearch(addPath(dir, l), name, ext);
350 // Then the name without country code
351 string const shortl = token(l, '_', 0);
353 tmp = libFileSearch(addPath(dir, shortl), name, ext);
359 // For compatibility, to be removed later (JMarc)
360 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
363 lyxerr << "i18nLibFileSearch: File `" << tmp
364 << "' has been found by the old method" <<endl;
368 lang = split(lang, l, ':');
371 return libFileSearch(dir, name, ext);
375 string const libScriptSearch(string const & command_in, quote_style style)
377 static string const token_scriptpath = "$$s/";
379 string command = command_in;
380 // Find the starting position of "$$s/"
381 string::size_type const pos1 = command.find(token_scriptpath);
382 if (pos1 == string::npos)
384 // Find the end of the "$$s/some_subdir/some_script" word within
385 // command. Assumes that the script name does not contain spaces.
386 string::size_type const start_script = pos1 + 4;
387 string::size_type const pos2 = command.find(' ', start_script);
388 string::size_type const size_script = pos2 == string::npos?
389 (command.size() - start_script) : pos2 - start_script;
391 // Does this script file exist?
392 string const script =
393 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
395 if (script.empty()) {
396 // Replace "$$s/" with ""
397 command.erase(pos1, 4);
399 // Replace "$$s/foo/some_script" with "<path to>/some_script".
400 string::size_type const size_replace = size_script + 4;
401 command.replace(pos1, size_replace, quoteName(script, style));
410 FileName const createTmpDir(FileName const & tempdir, string const & mask)
413 << "createTmpDir: tempdir=`" << tempdir << "'\n"
414 << "createTmpDir: mask=`" << mask << '\'' << endl;
416 FileName const tmpfl(tempName(tempdir, mask));
417 // lyx::tempName actually creates a file to make sure that it
418 // stays unique. So we have to delete it before we can create
419 // a dir with the same name. Note also that we are not thread
420 // safe because of the gap between unlink and mkdir. (Lgb)
423 if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
424 lyxerr << "LyX could not create the temporary directory '"
425 << tmpfl << "'" << endl;
435 bool destroyDir(FileName const & tmpdir)
438 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
439 } catch (fs::filesystem_error const & fe){
440 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
446 string const createBufferTmpDir()
449 // We are in our own directory. Why bother to mangle name?
450 // In fact I wrote this code to circumvent a problematic behaviour
451 // (bug?) of EMX mkstemp().
453 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
454 convert<string>(count++);
456 if (mkdir(FileName(tmpfl), 0777)) {
457 lyxerr << "LyX could not create the temporary directory '"
458 << tmpfl << "'" << endl;
465 FileName const createLyXTmpDir(FileName const & deflt)
467 if (!deflt.empty() && deflt.absFilename() != "/tmp") {
468 if (mkdir(deflt, 0777)) {
469 if (isDirWriteable(deflt)) {
470 // deflt could not be created because it
471 // did exist already, so let's create our own
473 return createTmpDir(deflt, "lyx_tmpdir");
475 // some other error occured.
476 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
481 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
486 bool createDirectory(FileName const & path, int permission)
488 BOOST_ASSERT(!path.empty());
489 return mkdir(path, permission) == 0;
493 // Strip filename from path name
494 string const onlyPath(string const & filename)
496 // If empty filename, return empty
497 if (filename.empty())
500 // Find last / or start of filename
501 string::size_type j = filename.rfind('/');
502 return j == string::npos ? "./" : filename.substr(0, j + 1);
506 // Convert relative path into absolute path based on a basepath.
507 // If relpath is absolute, just use that.
508 // If basepath is empty, use CWD as base.
509 FileName const makeAbsPath(string const & relPath, string const & basePath)
511 // checks for already absolute path
512 if (os::is_absolute_path(relPath))
513 return FileName(relPath);
515 // Copies given paths
516 string tempRel = os::internal_path(relPath);
517 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
518 tempRel = subst(tempRel, "//", "/");
522 if (os::is_absolute_path(basePath))
525 tempBase = addPath(getcwd().absFilename(), basePath);
527 // Handle /./ at the end of the path
528 while (suffixIs(tempBase, "/./"))
529 tempBase.erase(tempBase.length() - 2);
531 // processes relative path
532 string rTemp = tempRel;
535 while (!rTemp.empty()) {
537 rTemp = split(rTemp, temp, '/');
539 if (temp == ".") continue;
541 // Remove one level of TempBase
542 string::difference_type i = tempBase.length() - 2;
545 while (i > 0 && tempBase[i] != '/')
548 tempBase.erase(i, string::npos);
551 } else if (temp.empty() && !rTemp.empty()) {
552 tempBase = os::current_root() + rTemp;
555 // Add this piece to TempBase
556 if (!suffixIs(tempBase, '/'))
562 // returns absolute path
563 return FileName(os::internal_path(tempBase));
567 // Correctly append filename to the pathname.
568 // If pathname is '.', then don't use pathname.
569 // Chops any path of filename.
570 string const addName(string const & path, string const & fname)
572 string const basename = onlyFilename(fname);
575 if (path != "." && path != "./" && !path.empty()) {
576 buf = os::internal_path(path);
577 if (!suffixIs(path, '/'))
581 return buf + basename;
585 // Strips path from filename
586 string const onlyFilename(string const & fname)
591 string::size_type j = fname.rfind('/');
592 if (j == string::npos) // no '/' in fname
596 return fname.substr(j + 1);
600 /// Returns true is path is absolute
601 bool absolutePath(string const & path)
603 return os::is_absolute_path(path);
607 // Create absolute path. If impossible, don't do anything
608 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
609 string const expandPath(string const & path)
611 // checks for already absolute path
612 string rTemp = replaceEnvironmentPath(path);
613 if (os::is_absolute_path(rTemp))
617 string const copy = rTemp;
620 rTemp = split(rTemp, temp, '/');
623 return getcwd().absFilename() + '/' + rTemp;
626 return package().home_dir().absFilename() + '/' + rTemp;
629 return makeAbsPath(copy).absFilename();
631 // Don't know how to handle this
636 // Normalize a path. Constracts path/../path
637 // Can't handle "../../" or "/../" (Asger)
638 // Also converts paths like /foo//bar ==> /foo/bar
639 string const normalizePath(string const & path)
641 // Normalize paths like /foo//bar ==> /foo/bar
642 static boost::regex regex("/{2,}");
643 string const tmppath = boost::regex_merge(path, regex, "/");
645 fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
647 if (!npath.is_complete())
648 return "./" + npath.string() + '/';
650 return npath.string() + '/';
654 string const getFileContents(FileName const & fname)
656 string const encodedname = fname.toFilesystemEncoding();
657 if (fs::exists(encodedname)) {
658 ifstream ifs(encodedname.c_str());
666 lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
671 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
672 string const replaceEnvironmentPath(string const & path)
674 // ${VAR} is defined as
675 // $\{[A-Za-z_][A-Za-z_0-9]*\}
676 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
678 // $VAR is defined as:
679 // $\{[A-Za-z_][A-Za-z_0-9]*\}
680 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
682 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
683 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
686 string result = path;
688 regex_match(result, what, envvar_br_re);
689 if (!what[0].matched) {
690 regex_match(result, what, envvar_re);
691 if (!what[0].matched)
694 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
700 // Make relative path out of two absolute paths
701 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
702 // Makes relative path out of absolute path. If it is deeper than basepath,
703 // it's easy. If basepath and abspath share something (they are all deeper
704 // than some directory), it'll be rendered using ..'s. If they are completely
705 // different, then the absolute path will be used as relative path.
707 docstring::size_type const abslen = abspath.length();
708 docstring::size_type const baselen = basepath.length();
710 docstring::size_type i = os::common_path(abspath, basepath);
713 // actually no match - cannot make it relative
717 // Count how many dirs there are in basepath above match
718 // and append as many '..''s into relpath
720 docstring::size_type j = i;
721 while (j < baselen) {
722 if (basepath[j] == '/') {
723 if (j + 1 == baselen)
730 // Append relative stuff from common directory to abspath
731 if (abspath[i] == '/')
733 for (; i < abslen; ++i)
736 if (suffixIs(buf, '/'))
737 buf.erase(buf.length() - 1);
738 // Substitute empty with .
745 // Append sub-directory(ies) to a path in an intelligent way
746 string const addPath(string const & path, string const & path_2)
749 string const path2 = os::internal_path(path_2);
751 if (!path.empty() && path != "." && path != "./") {
752 buf = os::internal_path(path);
753 if (path[path.length() - 1] != '/')
757 if (!path2.empty()) {
758 string::size_type const p2start = path2.find_first_not_of('/');
759 string::size_type const p2end = path2.find_last_not_of('/');
760 string const tmp = path2.substr(p2start, p2end - p2start + 1);
767 string const changeExtension(string const & oldname, string const & extension)
769 string::size_type const last_slash = oldname.rfind('/');
770 string::size_type last_dot = oldname.rfind('.');
771 if (last_dot < last_slash && last_slash != string::npos)
772 last_dot = string::npos;
775 // Make sure the extension starts with a dot
776 if (!extension.empty() && extension[0] != '.')
777 ext= '.' + extension;
781 return os::internal_path(oldname.substr(0, last_dot) + ext);
785 string const removeExtension(string const & name)
787 return changeExtension(name, string());
791 string const addExtension(string const & name, string const & extension)
793 if (!extension.empty() && extension[0] != '.')
794 return name + '.' + extension;
795 return name + extension;
799 /// Return the extension of the file (not including the .)
800 string const getExtension(string const & name)
802 string::size_type const last_slash = name.rfind('/');
803 string::size_type const last_dot = name.rfind('.');
804 if (last_dot != string::npos &&
805 (last_slash == string::npos || last_dot > last_slash))
806 return name.substr(last_dot + 1,
807 name.length() - (last_dot + 1));
813 // the different filetypes and what they contain in one of the first lines
814 // (dots are any characters). (Herbert 20020131)
817 // EPS %!PS-Adobe-3.0 EPSF...
824 // PBM P1... or P4 (B/W)
825 // PGM P2... or P5 (Grayscale)
826 // PPM P3... or P6 (color)
827 // PS %!PS-Adobe-2.0 or 1.0, no "EPSF"!
828 // SGI \001\332... (decimal 474)
830 // TIFF II... or MM...
832 // XPM /* XPM */ sometimes missing (f.ex. tgif-export)
833 // ...static char *...
834 // XWD \000\000\000\151 (0x00006900) decimal 105
836 // GZIP \037\213 http://www.ietf.org/rfc/rfc1952.txt
837 // ZIP PK... http://www.halyava.ru/document/ind_arch.htm
838 // Z \037\235 UNIX compress
840 string const getFormatFromContents(FileName const & filename)
843 if (filename.empty() || !isFileReadable(filename))
846 ifstream ifs(filename.toFilesystemEncoding().c_str());
848 // Couldn't open file...
852 static string const gzipStamp = "\037\213";
855 static string const zipStamp = "PK";
858 static string const compressStamp = "\037\235";
860 // Maximum strings to read
861 int const max_count = 50;
866 bool firstLine = true;
867 while ((count++ < max_count) && format.empty()) {
869 LYXERR(Debug::GRAPHICS)
870 << "filetools(getFormatFromContents)\n"
871 << "\tFile type not recognised before EOF!"
877 string const stamp = str.substr(0, 2);
878 if (firstLine && str.size() >= 2) {
879 // at first we check for a zipped file, because this
880 // information is saved in the first bytes of the file!
881 // also some graphic formats which save the information
882 // in the first line, too.
883 if (prefixIs(str, gzipStamp)) {
886 } else if (stamp == zipStamp) {
889 } else if (stamp == compressStamp) {
893 } else if (stamp == "BM") {
896 } else if (stamp == "\001\332") {
900 // Don't need to use str.at(0), str.at(1) because
901 // we already know that str.size() >= 2
902 } else if (str[0] == 'P') {
918 } else if ((stamp == "II") || (stamp == "MM")) {
921 } else if (prefixIs(str,"%TGIF")) {
924 } else if (prefixIs(str,"#FIG")) {
927 } else if (prefixIs(str,"GIF")) {
930 } else if (str.size() > 3) {
931 int const c = ((str[0] << 24) & (str[1] << 16) &
932 (str[2] << 8) & str[3]);
943 else if (contains(str,"EPSF"))
944 // dummy, if we have wrong file description like
945 // %!PS-Adobe-2.0EPSF"
948 else if (contains(str,"Grace"))
951 else if (contains(str,"JFIF"))
954 else if (contains(str,"%PDF"))
957 else if (contains(str,"PNG"))
960 else if (contains(str,"%!PS-Adobe")) {
963 if (contains(str,"EPSF"))
969 else if (contains(str,"_bits[]"))
972 else if (contains(str,"XPM") || contains(str, "static char *"))
975 else if (contains(str,"BITPIX"))
979 if (!format.empty()) {
980 LYXERR(Debug::GRAPHICS)
981 << "Recognised Fileformat: " << format << endl;
985 LYXERR(Debug::GRAPHICS)
986 << "filetools(getFormatFromContents)\n"
987 << "\tCouldn't find a known format!\n";
992 /// check for zipped file
993 bool zippedFile(FileName const & name)
995 string const type = getFormatFromContents(name);
996 if (contains("gzip zip compress", type) && !type.empty())
1002 string const unzippedFileName(string const & zipped_file)
1004 string const ext = getExtension(zipped_file);
1005 if (ext == "gz" || ext == "z" || ext == "Z")
1006 return changeExtension(zipped_file, string());
1007 return "unzipped_" + zipped_file;
1011 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1013 FileName const tempfile = FileName(unzipped_file.empty() ?
1014 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1017 string const command = "gunzip -c " +
1018 zipped_file.toFilesystemEncoding() + " > " +
1019 tempfile.toFilesystemEncoding();
1021 one.startscript(Systemcall::Wait, command);
1022 // test that command was executed successfully (anon)
1023 // yes, please do. (Lgb)
1028 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1032 // If file is from LyXDir, display it as if it were relative.
1033 string const system = package().system_support().absFilename();
1034 if (prefixIs(str, system) && str != system)
1035 return from_utf8("[" + str.erase(0, system.length()) + "]");
1037 // replace /home/blah with ~/
1038 string const home = package().home_dir().absFilename();
1039 if (!home.empty() && prefixIs(str, home))
1040 str = subst(str, home, "~");
1042 if (str.length() <= threshold)
1043 return from_utf8(os::external_path(str));
1045 string const prefix = ".../";
1048 while (str.length() > threshold)
1049 str = split(str, temp, '/');
1051 // Did we shorten everything away?
1053 // Yes, filename itself is too long.
1054 // Pick the start and the end of the filename.
1055 str = onlyFilename(path);
1056 string const head = str.substr(0, threshold / 2 - 3);
1058 string::size_type len = str.length();
1060 str.substr(len - threshold / 2 - 2, len - 1);
1061 str = head + "..." + tail;
1064 return from_utf8(os::external_path(prefix + str));
1068 bool readLink(FileName const & file, FileName & link)
1070 #ifdef HAVE_READLINK
1071 char linkbuffer[512];
1072 // Should be PATH_MAX but that needs autconf support
1073 string const encoded = file.toFilesystemEncoding();
1074 int const nRead = ::readlink(encoded.c_str(),
1075 linkbuffer, sizeof(linkbuffer) - 1);
1078 linkbuffer[nRead] = '\0'; // terminator
1079 link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1087 cmd_ret const runCommand(string const & cmd)
1089 // FIXME: replace all calls to RunCommand with ForkedCall
1090 // (if the output is not needed) or the code in ISpell.cpp
1091 // (if the output is needed).
1093 // One question is if we should use popen or
1094 // create our own popen based on fork, exec, pipe
1095 // of course the best would be to have a
1096 // pstream (process stream), with the
1097 // variants ipstream, opstream
1099 #if defined (HAVE_POPEN)
1100 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1101 #elif defined (HAVE__POPEN)
1102 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1104 #error No popen() function.
1107 // (Claus Hentschel) Check if popen was succesful ;-)
1109 lyxerr << "RunCommand:: could not start child process" << endl;
1110 return make_pair(-1, string());
1116 ret += static_cast<char>(c);
1120 #if defined (HAVE_PCLOSE)
1121 int const pret = pclose(inf);
1122 #elif defined (HAVE__PCLOSE)
1123 int const pret = _pclose(inf);
1125 #error No pclose() function.
1129 perror("RunCommand:: could not terminate child process");
1131 return make_pair(pret, ret);
1135 FileName const findtexfile(string const & fil, string const & /*format*/)
1137 /* There is no problem to extend this function too use other
1138 methods to look for files. It could be setup to look
1139 in environment paths and also if wanted as a last resort
1140 to a recursive find. One of the easier extensions would
1141 perhaps be to use the LyX file lookup methods. But! I am
1142 going to implement this until I see some demand for it.
1146 // If the file can be found directly, we just return a
1147 // absolute path version of it.
1148 FileName const absfile(makeAbsPath(fil));
1149 if (fs::exists(absfile.toFilesystemEncoding()))
1152 // No we try to find it using kpsewhich.
1153 // It seems from the kpsewhich manual page that it is safe to use
1154 // kpsewhich without --format: "When the --format option is not
1155 // given, the search path used when looking for a file is inferred
1156 // from the name given, by looking for a known extension. If no
1157 // known extension is found, the search path for TeX source files
1159 // However, we want to take advantage of the format sine almost all
1160 // the different formats has environment variables that can be used
1161 // to controll which paths to search. f.ex. bib looks in
1162 // BIBINPUTS and TEXBIB. Small list follows:
1163 // bib - BIBINPUTS, TEXBIB
1165 // graphic/figure - TEXPICTS, TEXINPUTS
1166 // ist - TEXINDEXSTYLE, INDEXSTYLE
1167 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1169 // tfm - TFMFONTS, TEXFONTS
1170 // This means that to use kpsewhich in the best possible way we
1171 // should help it by setting additional path in the approp. envir.var.
1172 string const kpsecmd = "kpsewhich " + fil;
1174 cmd_ret const c = runCommand(kpsecmd);
1176 LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1177 << "kpse result = `" << rtrim(c.second, "\n\r")
1180 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1187 void removeAutosaveFile(string const & filename)
1189 string a = onlyPath(filename);
1191 a += onlyFilename(filename);
1193 FileName const autosave(a);
1194 if (fs::exists(autosave.toFilesystemEncoding()))
1199 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1200 string const & message)
1202 LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1203 << message << std::endl;
1204 // FIXME: Why is this func deleting a file? (Lgb)
1210 string const readBB_from_PSFile(FileName const & file)
1212 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1213 // It seems that every command in the header has an own line,
1214 // getline() should work for all files.
1215 // On the other hand some plot programs write the bb at the
1216 // end of the file. Than we have in the header:
1217 // %%BoundingBox: (atend)
1218 // In this case we must check the end.
1219 bool zipped = zippedFile(file);
1220 FileName const file_ = zipped ? unzipFile(file) : file;
1221 string const format = getFormatFromContents(file_);
1223 if (format != "eps" && format != "ps") {
1224 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1228 static boost::regex bbox_re(
1229 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1230 std::ifstream is(file_.toFilesystemEncoding().c_str());
1235 if (regex_match(s, what, bbox_re)) {
1236 // Our callers expect the tokens in the string
1237 // separated by single spaces.
1238 // FIXME: change return type from string to something
1241 os << what.str(1) << ' ' << what.str(2) << ' '
1242 << what.str(3) << ' ' << what.str(4);
1243 string const bb = os.str();
1244 readBB_lyxerrMessage(file_, zipped, bb);
1248 readBB_lyxerrMessage(file_, zipped, "no bb found");
1253 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1255 // If the original is newer than the copy, then copy the original
1256 // to the new directory.
1258 string const file1 = filename1.toFilesystemEncoding();
1259 string const file2 = filename2.toFilesystemEncoding();
1261 if (fs::exists(file1) && fs::exists(file2)) {
1262 double const tmp = difftime(fs::last_write_time(file1),
1263 fs::last_write_time(file2));
1265 cmp = tmp > 0 ? 1 : -1;
1267 } else if (fs::exists(file1)) {
1269 } else if (fs::exists(file2)) {
1276 // the following is adapted from zlib-1.2.3/contrib/minizip.c
1277 // and miniunz.c, except that
1278 // 1. mkdir, makedir is replaced by lyx' own version
1279 // 2. printf is replaced by lyxerr
1282 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1288 WIN32_FIND_DATA ff32;
1290 hFind = FindFirstFile(f,&ff32);
1291 if (hFind != INVALID_HANDLE_VALUE)
1293 FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1294 FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1304 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1307 struct stat s; /* results of stat() */
1308 struct tm* filedate;
1311 if (strcmp(f,"-")!=0) {
1312 char name[MAXFILENAME+1];
1313 int len = strlen(f);
1314 if (len > MAXFILENAME)
1317 strncpy(name, f,MAXFILENAME-1);
1318 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1319 name[ MAXFILENAME ] = '\0';
1321 if (name[len - 1] == '/')
1322 name[len - 1] = '\0';
1323 /* not all systems allow stat'ing a file with / appended */
1324 if (stat(name,&s)==0) {
1329 filedate = localtime(&tm_t);
1331 tmzip->tm_sec = filedate->tm_sec;
1332 tmzip->tm_min = filedate->tm_min;
1333 tmzip->tm_hour = filedate->tm_hour;
1334 tmzip->tm_mday = filedate->tm_mday;
1335 tmzip->tm_mon = filedate->tm_mon ;
1336 tmzip->tm_year = filedate->tm_year;
1343 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1350 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1357 int size_buf = WRITEBUFFERSIZE;
1358 buf = (void*)malloc(size_buf);
1360 lyxerr << "Error allocating memory" << endl;
1363 string const zfile = zipfile.toFilesystemEncoding();
1364 const char * fname = zfile.c_str();
1366 #ifdef USEWIN32IOAPI
1367 zlib_filefunc_def ffunc;
1368 fill_win32_filefunc(&ffunc);
1369 // false: not append
1370 zf = zipOpen2(fname, false, NULL, &ffunc);
1372 zf = zipOpen(fname, false);
1376 lyxerr << "error opening " << zipfile << endl;
1380 for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1384 const char * diskfilename = it->first.c_str();
1385 const char * filenameinzip = it->second.c_str();
1386 unsigned long crcFile=0;
1388 zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
1389 zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
1393 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1395 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1396 NULL,0,NULL,0,NULL /* comment*/,
1398 Z_DEFAULT_COMPRESSION, // compression level
1400 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1401 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1405 if (err != ZIP_OK) {
1406 lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1409 fin = fopen(diskfilename, "rb");
1411 lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1417 size_read = (int)fread(buf, 1, size_buf, fin);
1418 if (size_read < size_buf)
1420 lyxerr << "error in reading " << filenameinzip << endl;
1425 err = zipWriteInFileInZip (zf, buf, size_read);
1427 lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1431 } while ((err == ZIP_OK) && (size_read>0));
1436 err = zipCloseFileInZip(zf);
1437 if (err != ZIP_OK) {
1438 lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1442 errclose = zipClose(zf, NULL);
1443 if (errclose != ZIP_OK) {
1444 lyxerr << "error in closing " << zipfile << endl;
1451 // adapted from miniunz.c
1453 /* change_file_date : change the date/time of a file
1454 filename : the filename of the file where date/time must be modified
1455 dosdate : the new date at the MSDos format (4 bytes)
1456 tmu_date : the SAME new date at the tm_unz format */
1457 void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date)
1461 FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1463 hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE,
1464 0,NULL,OPEN_EXISTING,0,NULL);
1465 GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
1466 DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
1467 LocalFileTimeToFileTime(&ftLocal,&ftm);
1468 SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
1475 newdate.tm_sec = tmu_date.tm_sec;
1476 newdate.tm_min=tmu_date.tm_min;
1477 newdate.tm_hour=tmu_date.tm_hour;
1478 newdate.tm_mday=tmu_date.tm_mday;
1479 newdate.tm_mon=tmu_date.tm_mon;
1480 if (tmu_date.tm_year > 1900)
1481 newdate.tm_year=tmu_date.tm_year - 1900;
1483 newdate.tm_year=tmu_date.tm_year ;
1484 newdate.tm_isdst=-1;
1486 ut.actime=ut.modtime=mktime(&newdate);
1487 utime(filename,&ut);
1493 int do_extract_currentfile(unzFile uf,
1494 const int * popt_extract_without_path,
1495 int * popt_overwrite,
1496 const char * password,
1497 const char * dirname)
1499 char filename_inzip[256];
1500 char* filename_withoutpath;
1507 unz_file_info file_info;
1509 err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1512 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1516 size_buf = WRITEBUFFERSIZE;
1517 buf = (void*)malloc(size_buf);
1519 lyxerr << "Error allocating memory" << endl;
1520 return UNZ_INTERNALERROR;
1523 p = filename_withoutpath = filename_inzip;
1524 while ((*p) != '\0') {
1525 if (((*p)=='/') || ((*p)=='\\'))
1526 filename_withoutpath = p+1;
1529 // this is a directory
1530 if ((*filename_withoutpath)=='\0') {
1531 if ((*popt_extract_without_path)==0)
1532 makedir(filename_inzip);
1534 // this is a filename
1536 char write_filename[1024];
1538 strcpy(write_filename, dirname);
1539 int len = strlen(write_filename);
1540 if (write_filename[len-1] != '\\' &&
1541 write_filename[len-1] != '/')
1542 strcat(write_filename, "/");
1544 if ((*popt_extract_without_path)==0)
1545 strcat(write_filename, filename_inzip);
1547 strcat(write_filename, filename_withoutpath);
1549 err = unzOpenCurrentFilePassword(uf,password);
1551 lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1553 fout=fopen(write_filename, "wb");
1555 /* some zipfile don't contain directory alone before file */
1556 if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
1557 (filename_withoutpath!=(char*)filename_inzip)) {
1558 char c=*(filename_withoutpath-1);
1559 *(filename_withoutpath-1)='\0';
1560 makedir(write_filename);
1561 *(filename_withoutpath-1)=c;
1562 fout=fopen(write_filename,"wb");
1566 lyxerr << "error opening " << write_filename << endl;
1571 LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1574 err = unzReadCurrentFile(uf,buf,size_buf);
1576 lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1580 if (fwrite(buf,err,1,fout)!=1) {
1581 lyxerr << "error in writing extracted file" << endl;
1590 change_file_date(write_filename,file_info.dosDate,
1591 file_info.tmu_date);
1595 err = unzCloseCurrentFile (uf);
1597 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1601 unzCloseCurrentFile(uf); /* don't lose the error */
1609 bool unzipToDir(string const & zipfile, string const & dirname)
1612 #ifdef USEWIN32IOAPI
1613 zlib_filefunc_def ffunc;
1616 const char * zipfilename = zipfile.c_str();
1618 #ifdef USEWIN32IOAPI
1619 fill_win32_filefunc(&ffunc);
1620 uf = unzOpen2(zipfilename, &ffunc);
1622 uf = unzOpen(zipfilename);
1626 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1634 int opt_extract_without_path = 0;
1635 int opt_overwrite = 1;
1636 char * password = NULL;
1638 err = unzGetGlobalInfo (uf, &gi);
1639 if (err != UNZ_OK) {
1640 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1644 for (i=0; i < gi.number_entry; i++) {
1645 if (do_extract_currentfile(uf, &opt_extract_without_path,
1647 password, dirname.c_str()) != UNZ_OK)
1650 if ((i+1)<gi.number_entry) {
1651 err = unzGoToNextFile(uf);
1652 if (err != UNZ_OK) {
1653 lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;;
1659 unzCloseCurrentFile(uf);
1662 } //namespace support