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