Tidy: modernize-use-nullptr
[alexxy/gromacs.git] / src / gromacs / utility / path.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2011,2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements functions in path.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "path.h"
45
46 #include "config.h"
47
48 #include <cctype>
49 #include <cerrno>
50 #include <cstddef>
51 #include <cstdio>
52 #include <cstdlib>
53 #include <cstring>
54
55 #include <algorithm>
56 #include <string>
57
58 #include <sys/stat.h>
59
60 #if GMX_NATIVE_WINDOWS
61 #include <Windows.h>
62 #include <direct.h>
63 #else
64 #ifdef HAVE_UNISTD_H
65 #include <unistd.h>
66 #endif
67 #endif
68
69 #include "gromacs/utility/dir_separator.h"
70 #include "gromacs/utility/exceptions.h"
71 #include "gromacs/utility/futil.h"
72 #include "gromacs/utility/stringutil.h"
73
74 namespace
75 {
76
77 //! Directory separator to use when joining paths.
78 const char cDirSeparator = '/';
79 //! Directory separators to use when parsing paths.
80 const char cDirSeparators[] = "/\\";
81 /*! \var cPathSeparator
82  * \brief
83  * Separator to use to split the PATH environment variable.
84  *
85  * When reading the PATH environment variable, Unix separates entries
86  * with colon, while windows uses semicolon.
87  */
88 #if GMX_NATIVE_WINDOWS
89 const char cPathSeparator = ';';
90 #else
91 const char cPathSeparator = ':';
92 #endif
93
94 //! Check whether a given character is a directory separator.
95 bool isDirSeparator(char chr)
96 {
97     return std::strchr(cDirSeparators, chr);
98 }
99
100 } // namespace
101
102 namespace gmx
103 {
104
105 /********************************************************************
106  * Path
107  */
108
109 bool Path::containsDirectory(const std::string &path)
110 {
111     return path.find_first_of(cDirSeparators) != std::string::npos;
112 }
113
114 /* Check if the program name begins with "/" on unix/cygwin, or
115  * with "\" or "X:\" on windows. If not, the program name
116  * is relative to the current directory.
117  */
118 bool Path::isAbsolute(const char *path)
119 {
120     if (isDirSeparator(path[0]))
121     {
122         return true;
123     }
124 #if GMX_NATIVE_WINDOWS
125     return path[0] != '\0' && path[1] == ':' && isDirSeparator(path[2]);
126 #else
127     return false;
128 #endif
129 }
130
131 bool Path::isAbsolute(const std::string &path)
132 {
133     return isAbsolute(path.c_str());
134 }
135
136 #if GMX_NATIVE_WINDOWS
137 namespace
138 {
139 struct handle_wrapper
140 {
141     HANDLE handle;
142     handle_wrapper(HANDLE h)
143         : handle(h){}
144     ~handle_wrapper()
145     {
146         if (handle != INVALID_HANDLE_VALUE)
147         {
148             ::CloseHandle(handle);
149         }
150     }
151 };
152 }
153 #endif
154
155 bool Path::isEquivalent(const std::string &path1, const std::string &path2)
156 {
157     //based on boost_1_56_0/libs/filesystem/src/operations.cpp under BSL
158 #if GMX_NATIVE_WINDOWS
159     // Note well: Physical location on external media is part of the
160     // equivalence criteria. If there are no open handles, physical location
161     // can change due to defragmentation or other relocations. Thus handles
162     // must be held open until location information for both paths has
163     // been retrieved.
164
165     // p2 is done first, so any error reported is for p1
166     // FixME: #1635
167     handle_wrapper h2(
168             CreateFile(
169                     path2.c_str(),
170                     0,
171                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
172                     0,
173                     OPEN_EXISTING,
174                     FILE_FLAG_BACKUP_SEMANTICS,
175                     0));
176
177     handle_wrapper h1(
178             CreateFile(
179                     path1.c_str(),
180                     0,
181                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
182                     0,
183                     OPEN_EXISTING,
184                     FILE_FLAG_BACKUP_SEMANTICS,
185                     0));
186
187     if (h1.handle == INVALID_HANDLE_VALUE
188         || h2.handle == INVALID_HANDLE_VALUE)
189     {
190         // if one is invalid and the other isn't, then they aren't equivalent,
191         // but if both are invalid then it is an error
192         if (h1.handle == INVALID_HANDLE_VALUE
193             && h2.handle == INVALID_HANDLE_VALUE)
194         {
195             GMX_THROW(FileIOError("Path::isEquivalent called with two invalid files"));
196         }
197
198         return false;
199     }
200
201     // at this point, both handles are known to be valid
202
203     BY_HANDLE_FILE_INFORMATION info1, info2;
204
205     if (!GetFileInformationByHandle(h1.handle, &info1))
206     {
207         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
208     }
209
210     if (!GetFileInformationByHandle(h2.handle, &info2))
211     {
212         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
213     }
214
215     // In theory, volume serial numbers are sufficient to distinguish between
216     // devices, but in practice VSN's are sometimes duplicated, so last write
217     // time and file size are also checked.
218     return
219         info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
220         && info1.nFileIndexHigh == info2.nFileIndexHigh
221         && info1.nFileIndexLow == info2.nFileIndexLow
222         && info1.nFileSizeHigh == info2.nFileSizeHigh
223         && info1.nFileSizeLow == info2.nFileSizeLow
224         && info1.ftLastWriteTime.dwLowDateTime
225         == info2.ftLastWriteTime.dwLowDateTime
226         && info1.ftLastWriteTime.dwHighDateTime
227         == info2.ftLastWriteTime.dwHighDateTime;
228 #else
229     struct stat s1, s2;
230     int         e2 = stat(path2.c_str(), &s2);
231     int         e1 = stat(path1.c_str(), &s1);
232
233     if (e1 != 0 || e2 != 0)
234     {
235         // if one is invalid and the other isn't then they aren't equivalent,
236         // but if both are invalid then it is an error.
237         if (e1 != 0 && e2 != 0)
238         {
239             GMX_THROW_WITH_ERRNO(
240                     FileIOError("Path::isEquivalent called with two invalid files"),
241                     "stat", errno);
242         }
243         return false;
244     }
245
246     // both stats now known to be valid
247     return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino
248            // According to the POSIX stat specs, "The st_ino and st_dev fields
249            // taken together uniquely identify the file within the system."
250            // Just to be sure, size and mod time are also checked.
251            && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime;
252 #endif
253 }
254
255 std::string Path::join(const std::string &path1,
256                        const std::string &path2)
257 {
258     // TODO: Remove extra separators if they are present in the input paths.
259     return path1 + cDirSeparator + path2;
260 }
261
262
263 std::string Path::join(const std::string &path1,
264                        const std::string &path2,
265                        const std::string &path3)
266 {
267     // TODO: Remove extra separators if they are present in the input paths.
268     return path1 + cDirSeparator + path2 + cDirSeparator + path3;
269 }
270
271 std::string Path::getParentPath(const std::string &path)
272 {
273     /* Expects that the path doesn't contain "." or "..". If used on a path for
274      * which this isn't guaranteed realpath needs to be called first. */
275     size_t pos = path.find_last_of(cDirSeparators);
276     if (pos == std::string::npos)
277     {
278         return std::string();
279     }
280     return path.substr(0, pos);
281 }
282
283 std::string Path::getFilename(const std::string &path)
284 {
285     size_t pos = path.find_last_of(cDirSeparators);
286     if (pos == std::string::npos)
287     {
288         return path;
289     }
290     return path.substr(pos+1);
291 }
292
293 bool Path::hasExtension(const std::string &path)
294 {
295     return getFilename(path).find('.') != std::string::npos;
296 }
297
298 std::string Path::stripExtension(const std::string &path)
299 {
300     size_t dirSeparatorPos = path.find_last_of(cDirSeparators);
301     size_t extPos          = path.find_last_of('.');
302     if (extPos == std::string::npos
303         || (dirSeparatorPos != std::string::npos && extPos < dirSeparatorPos))
304     {
305         return path;
306     }
307     return path.substr(0, extPos);
308 }
309
310 std::string Path::concatenateBeforeExtension(const std::string &input, const std::string &stringToAdd)
311 {
312     std::string output;
313     size_t      dirSeparatorPosition = input.find_last_of(cDirSeparators);
314     size_t      extSeparatorPosition = input.find_last_of('.');
315     bool        havePath             = (dirSeparatorPosition != std::string::npos);
316     // Make sure that if there's an extension-separator character,
317     // that it follows the last path-separator character (if any),
318     // before we interpret it as an extension separator.
319     bool haveExtension = (extSeparatorPosition != std::string::npos &&
320                           ((!havePath ||
321                             (extSeparatorPosition > dirSeparatorPosition))));
322     if (!haveExtension)
323     {
324         output = input + stringToAdd;
325     }
326     else
327     {
328         output  = input.substr(0, extSeparatorPosition);
329         output += stringToAdd;
330         output += std::string(input, extSeparatorPosition);
331     }
332     return output;
333 }
334
335 std::string Path::normalize(const std::string &path)
336 {
337     std::string result(path);
338     if (DIR_SEPARATOR != '/')
339     {
340         std::replace(result.begin(), result.end(), '/', DIR_SEPARATOR);
341     }
342     return result;
343 }
344
345 const char *Path::stripSourcePrefix(const char *path)
346 {
347     const char *fallback           = path;
348     const char *sep                = path + std::strlen(path);
349     bool        gromacsSubdirFound = false;
350     while (sep > path)
351     {
352         const char *prevSep = sep - 1;
353         while (prevSep >= path && !isDirSeparator(*prevSep))
354         {
355             --prevSep;
356         }
357         const std::ptrdiff_t length = sep - prevSep - 1;
358         if (gromacsSubdirFound)
359         {
360             if (std::strncmp(prevSep + 1, "src", length) == 0)
361             {
362                 return prevSep + 1;
363             }
364             return fallback;
365         }
366         if (std::strncmp(prevSep + 1, "gromacs", length) == 0
367             || std::strncmp(prevSep + 1, "programs", length) == 0
368             || std::strncmp(prevSep + 1, "testutils", length) == 0)
369         {
370             gromacsSubdirFound = true;
371         }
372         if (fallback == path)
373         {
374             fallback = prevSep + 1;
375         }
376         sep = prevSep;
377     }
378     return fallback;
379 }
380
381 bool Path::exists(const char *path)
382 {
383     return gmx_fexist(path);
384 }
385
386 bool Path::exists(const std::string &path)
387 {
388     return exists(path.c_str());
389 }
390
391 std::string Path::getWorkingDirectory()
392 {
393     // TODO: Use exceptions instead of gmx_fatal().
394     char cwd[GMX_PATH_MAX];
395     gmx_getcwd(cwd, sizeof(cwd));
396     return cwd;
397 }
398
399 void Path::splitPathEnvironment(const std::string        &pathEnv,
400                                 std::vector<std::string> *result)
401 {
402     size_t prevPos = 0;
403     size_t separator;
404     do
405     {
406         separator = pathEnv.find(cPathSeparator, prevPos);
407         result->push_back(pathEnv.substr(prevPos, separator - prevPos));
408         prevPos = separator + 1;
409     }
410     while (separator != std::string::npos);
411 }
412
413 std::vector<std::string> Path::getExecutablePaths()
414 {
415     std::vector<std::string> result;
416 #if GMX_NATIVE_WINDOWS
417     // Add the local dir since it is not in the path on Windows.
418     result.push_back("");
419 #endif
420     const char *path = std::getenv("PATH");
421     if (path != nullptr)
422     {
423         splitPathEnvironment(path, &result);
424     }
425     return result;
426 }
427
428 std::string Path::resolveSymlinks(const std::string &path)
429 {
430     /* Does not fully resolve the path like realpath/boost::canonical would.
431      * It doesn't resolve path elements (including "." or ".."), but only
432      * resolves the entire path (it does that recursively). */
433     std::string result(path);
434 #if !GMX_NATIVE_WINDOWS
435     char        buf[GMX_PATH_MAX];
436     int         length;
437     while ((length = readlink(result.c_str(), buf, sizeof(buf)-1)) > 0)
438     {
439         buf[length] = '\0';
440         if (isAbsolute(buf))
441         {
442             result = buf;
443         }
444         else
445         {
446             result = join(getParentPath(result), buf);
447         }
448     }
449 #endif
450     return result;
451 }
452
453 /********************************************************************
454  * File
455  */
456
457 void File::returnFalseOnError(const NotFoundInfo & /*info*/)
458 {
459 }
460
461 void File::throwOnError(const NotFoundInfo &info)
462 {
463     if (info.wasError)
464     {
465         const std::string message
466             = formatString("Failed to access file '%s'.\n%s",
467                            info.filename, info.message);
468         GMX_THROW_WITH_ERRNO(FileIOError(message), info.call, info.err);
469     }
470 }
471
472 void File::throwOnNotFound(const NotFoundInfo &info)
473 {
474     throwOnError(info);
475     const std::string message
476         = formatString("File '%s' does not exist or is not accessible.\n%s",
477                        info.filename, info.message);
478     GMX_THROW_WITH_ERRNO(InvalidInputError(message), info.call, info.err);
479 }
480
481 // static
482 bool File::exists(const char *filename, NotFoundHandler onNotFound)
483 {
484     if (filename == nullptr)
485     {
486         return false;
487     }
488     FILE *test = std::fopen(filename, "r");
489     if (test == nullptr)
490     {
491         const bool   wasError = (errno != ENOENT && errno != ENOTDIR);
492         NotFoundInfo info(filename, "The file could not be opened.",
493                           "fopen", wasError, errno);
494         onNotFound(info);
495         return false;
496     }
497     else
498     {
499         std::fclose(test);
500         // Windows doesn't allow fopen of directory, so we don't need to check
501         // this separately.
502 #if !GMX_NATIVE_WINDOWS
503         struct stat st_buf;
504         int         status = stat(filename, &st_buf);
505         if (status != 0)
506         {
507             NotFoundInfo info(filename, "File information could not be read.",
508                               "stat", true, errno);
509             onNotFound(info);
510             return false;
511         }
512         if (!S_ISREG(st_buf.st_mode))
513         {
514             NotFoundInfo info(filename, "The file is not a regular file.",
515                               nullptr, true, 0);
516             onNotFound(info);
517             return false;
518         }
519 #endif
520         return true;
521     }
522 }
523
524 // static
525 bool File::exists(const std::string &filename, NotFoundHandler onNotFound)
526 {
527     return exists(filename.c_str(), onNotFound);
528 }
529
530 /********************************************************************
531  * Directory
532  */
533
534 int Directory::create(const char *path)
535 {
536     if (Directory::exists(path))
537     {
538         return 0;
539     }
540 #if GMX_NATIVE_WINDOWS
541     if (_mkdir(path))
542 #else
543     if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH) != 0)
544 #endif
545     {
546         // TODO: Proper error handling.
547         return -1;
548     }
549     return 0;
550 }
551
552
553 int Directory::create(const std::string &path)
554 {
555     return create(path.c_str());
556 }
557
558
559 bool Directory::exists(const char *path)
560 {
561     struct stat info;
562     if (stat(path, &info) != 0)
563     {
564         if (errno != ENOENT && errno != ENOTDIR)
565         {
566             // TODO: Proper error handling.
567         }
568         return false;
569     }
570 #if GMX_NATIVE_WINDOWS
571     return ((_S_IFDIR & info.st_mode) != 0);
572 #else
573     return S_ISDIR(info.st_mode);
574 #endif
575 }
576
577
578 bool Directory::exists(const std::string &path)
579 {
580     return exists(path.c_str());
581 }
582
583 } // namespace gmx