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