Remove gmx::File (except for File::exists())
[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, 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 <cstdio>
51 #include <cstdlib>
52 #include <cstring>
53
54 #include <algorithm>
55 #include <string>
56
57 #include <sys/stat.h>
58
59 #ifdef GMX_NATIVE_WINDOWS
60 #include <Windows.h>
61 #include <direct.h>
62 #else
63 #ifdef HAVE_UNISTD_H
64 #include <unistd.h>
65 #endif
66 #endif
67
68 #include "gromacs/utility/dir_separator.h"
69 #include "gromacs/utility/exceptions.h"
70 #include "gromacs/utility/futil.h"
71 #include "gromacs/utility/stringutil.h"
72
73 namespace
74 {
75
76 //! Directory separator to use when joining paths.
77 const char cDirSeparator = '/';
78 //! Directory separators to use when parsing paths.
79 const char cDirSeparators[] = "/\\";
80 /*! \var cPathSeparator
81  * \brief
82  * Separator to use to split the PATH environment variable.
83  *
84  * When reading the PATH environment variable, Unix separates entries
85  * with colon, while windows uses semicolon.
86  */
87 #ifdef GMX_NATIVE_WINDOWS
88 const char cPathSeparator = ';';
89 #else
90 const char cPathSeparator = ':';
91 #endif
92
93 //! Check whether a given character is a directory separator.
94 bool isDirSeparator(char chr)
95 {
96     return std::strchr(cDirSeparators, chr);
97 }
98
99 } // namespace
100
101 namespace gmx
102 {
103
104 /********************************************************************
105  * Path
106  */
107
108 bool Path::containsDirectory(const std::string &path)
109 {
110     return path.find_first_of(cDirSeparators) != std::string::npos;
111 }
112
113 /* Check if the program name begins with "/" on unix/cygwin, or
114  * with "\" or "X:\" on windows. If not, the program name
115  * is relative to the current directory.
116  */
117 bool Path::isAbsolute(const char *path)
118 {
119     if (isDirSeparator(path[0]))
120     {
121         return true;
122     }
123 #ifdef GMX_NATIVE_WINDOWS
124     return path[0] != '\0' && path[1] == ':' && isDirSeparator(path[2]);
125 #else
126     return false;
127 #endif
128 }
129
130 bool Path::isAbsolute(const std::string &path)
131 {
132     return isAbsolute(path.c_str());
133 }
134
135 #ifdef GMX_NATIVE_WINDOWS
136 namespace
137 {
138 struct handle_wrapper
139 {
140     HANDLE handle;
141     handle_wrapper(HANDLE h)
142         : handle(h){}
143     ~handle_wrapper()
144     {
145         if (handle != INVALID_HANDLE_VALUE)
146         {
147             ::CloseHandle(handle);
148         }
149     }
150 };
151 }
152 #endif
153
154 bool Path::isEquivalent(const std::string &path1, const std::string &path2)
155 {
156     //based on boost_1_56_0/libs/filesystem/src/operations.cpp under BSL
157 #ifdef GMX_NATIVE_WINDOWS
158     // Note well: Physical location on external media is part of the
159     // equivalence criteria. If there are no open handles, physical location
160     // can change due to defragmentation or other relocations. Thus handles
161     // must be held open until location information for both paths has
162     // been retrieved.
163
164     // p2 is done first, so any error reported is for p1
165     // FixME: #1635
166     handle_wrapper h2(
167             CreateFile(
168                     path2.c_str(),
169                     0,
170                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
171                     0,
172                     OPEN_EXISTING,
173                     FILE_FLAG_BACKUP_SEMANTICS,
174                     0));
175
176     handle_wrapper h1(
177             CreateFile(
178                     path1.c_str(),
179                     0,
180                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
181                     0,
182                     OPEN_EXISTING,
183                     FILE_FLAG_BACKUP_SEMANTICS,
184                     0));
185
186     if (h1.handle == INVALID_HANDLE_VALUE
187         || h2.handle == INVALID_HANDLE_VALUE)
188     {
189         // if one is invalid and the other isn't, then they aren't equivalent,
190         // but if both are invalid then it is an error
191         if (h1.handle == INVALID_HANDLE_VALUE
192             && h2.handle == INVALID_HANDLE_VALUE)
193         {
194             GMX_THROW(FileIOError("Path::isEquivalent called with two invalid files"));
195         }
196
197         return false;
198     }
199
200     // at this point, both handles are known to be valid
201
202     BY_HANDLE_FILE_INFORMATION info1, info2;
203
204     if (!GetFileInformationByHandle(h1.handle, &info1))
205     {
206         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
207     }
208
209     if (!GetFileInformationByHandle(h2.handle, &info2))
210     {
211         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
212     }
213
214     // In theory, volume serial numbers are sufficient to distinguish between
215     // devices, but in practice VSN's are sometimes duplicated, so last write
216     // time and file size are also checked.
217     return
218         info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
219         && info1.nFileIndexHigh == info2.nFileIndexHigh
220         && info1.nFileIndexLow == info2.nFileIndexLow
221         && info1.nFileSizeHigh == info2.nFileSizeHigh
222         && info1.nFileSizeLow == info2.nFileSizeLow
223         && info1.ftLastWriteTime.dwLowDateTime
224         == info2.ftLastWriteTime.dwLowDateTime
225         && info1.ftLastWriteTime.dwHighDateTime
226         == info2.ftLastWriteTime.dwHighDateTime;
227 #else
228     struct stat s1, s2;
229     int         e2 = stat(path2.c_str(), &s2);
230     int         e1 = stat(path1.c_str(), &s1);
231
232     if (e1 != 0 || e2 != 0)
233     {
234         // if one is invalid and the other isn't then they aren't equivalent,
235         // but if both are invalid then it is an error.
236         if (e1 != 0 && e2 != 0)
237         {
238             GMX_THROW_WITH_ERRNO(
239                     FileIOError("Path::isEquivalent called with two invalid files"),
240                     "stat", errno);
241         }
242         return false;
243     }
244
245     // both stats now known to be valid
246     return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino
247            // According to the POSIX stat specs, "The st_ino and st_dev fields
248            // taken together uniquely identify the file within the system."
249            // Just to be sure, size and mod time are also checked.
250            && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime;
251 #endif
252 }
253
254 std::string Path::join(const std::string &path1,
255                        const std::string &path2)
256 {
257     // TODO: Remove extra separators if they are present in the input paths.
258     return path1 + cDirSeparator + path2;
259 }
260
261
262 std::string Path::join(const std::string &path1,
263                        const std::string &path2,
264                        const std::string &path3)
265 {
266     // TODO: Remove extra separators if they are present in the input paths.
267     return path1 + cDirSeparator + path2 + cDirSeparator + path3;
268 }
269
270 std::string Path::getParentPath(const std::string &path)
271 {
272     /* Expects that the path doesn't contain "." or "..". If used on a path for
273      * which this isn't guaranteed realpath needs to be called first. */
274     size_t pos = path.find_last_of(cDirSeparators);
275     if (pos == std::string::npos)
276     {
277         return std::string();
278     }
279     return path.substr(0, pos);
280 }
281
282 std::string Path::getFilename(const std::string &path)
283 {
284     size_t pos = path.find_last_of(cDirSeparators);
285     if (pos == std::string::npos)
286     {
287         return path;
288     }
289     return path.substr(pos+1);
290 }
291
292 bool Path::hasExtension(const std::string &path)
293 {
294     return getFilename(path).find('.') != std::string::npos;
295 }
296
297 std::string Path::stripExtension(const std::string &path)
298 {
299     size_t dirSeparatorPos = path.find_last_of(cDirSeparators);
300     size_t extPos          = path.find_last_of('.');
301     if (extPos == std::string::npos
302         || (dirSeparatorPos != std::string::npos && extPos < dirSeparatorPos))
303     {
304         return path;
305     }
306     return path.substr(0, extPos);
307 }
308
309 std::string Path::normalize(const std::string &path)
310 {
311     std::string result(path);
312     if (DIR_SEPARATOR != '/')
313     {
314         std::replace(result.begin(), result.end(), '/', DIR_SEPARATOR);
315     }
316     return result;
317 }
318
319 bool Path::exists(const char *path)
320 {
321     return gmx_fexist(path);
322 }
323
324 bool Path::exists(const std::string &path)
325 {
326     return exists(path.c_str());
327 }
328
329 std::string Path::getWorkingDirectory()
330 {
331     // TODO: Use exceptions instead of gmx_fatal().
332     char cwd[GMX_PATH_MAX];
333     gmx_getcwd(cwd, sizeof(cwd));
334     return cwd;
335 }
336
337 void Path::splitPathEnvironment(const std::string        &pathEnv,
338                                 std::vector<std::string> *result)
339 {
340     size_t prevPos = 0;
341     size_t separator;
342     do
343     {
344         separator = pathEnv.find(cPathSeparator, prevPos);
345         result->push_back(pathEnv.substr(prevPos, separator - prevPos));
346         prevPos = separator + 1;
347     }
348     while (separator != std::string::npos);
349 }
350
351 std::vector<std::string> Path::getExecutablePaths()
352 {
353     std::vector<std::string> result;
354 #ifdef GMX_NATIVE_WINDOWS
355     // Add the local dir since it is not in the path on Windows.
356     result.push_back("");
357 #endif
358     const char *path = std::getenv("PATH");
359     if (path != NULL)
360     {
361         splitPathEnvironment(path, &result);
362     }
363     return result;
364 }
365
366 std::string Path::resolveSymlinks(const std::string &path)
367 {
368     /* Does not fully resolve the path like realpath/boost::canonical would.
369      * It doesn't resolve path elements (including "." or ".."), but only
370      * resolves the entire path (it does that recursively). */
371     std::string result(path);
372 #ifndef GMX_NATIVE_WINDOWS
373     char        buf[GMX_PATH_MAX];
374     int         length;
375     while ((length = readlink(result.c_str(), buf, sizeof(buf)-1)) > 0)
376     {
377         buf[length] = '\0';
378         if (isAbsolute(buf))
379         {
380             result = buf;
381         }
382         else
383         {
384             result = join(getParentPath(result), buf);
385         }
386     }
387 #endif
388     return result;
389 }
390
391 /********************************************************************
392  * File
393  */
394
395 // static
396 bool File::exists(const char *filename)
397 {
398     if (filename == NULL)
399     {
400         return false;
401     }
402     FILE *test = std::fopen(filename, "r");
403     if (test == NULL)
404     {
405         return false;
406     }
407     else
408     {
409         std::fclose(test);
410         // Windows doesn't allow fopen of directory, so we don't need to check
411         // this separately.
412 #ifndef GMX_NATIVE_WINDOWS
413         struct stat st_buf;
414         int         status = stat(filename, &st_buf);
415         if (status != 0 || !S_ISREG(st_buf.st_mode))
416         {
417             return false;
418         }
419 #endif
420         return true;
421     }
422 }
423
424 // static
425 bool File::exists(const std::string &filename)
426 {
427     return exists(filename.c_str());
428 }
429
430 /********************************************************************
431  * Directory
432  */
433
434 int Directory::create(const char *path)
435 {
436     if (Directory::exists(path))
437     {
438         return 0;
439     }
440 #ifdef GMX_NATIVE_WINDOWS
441     if (_mkdir(path))
442 #else
443     if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH) != 0)
444 #endif
445     {
446         // TODO: Proper error handling.
447         return -1;
448     }
449     return 0;
450 }
451
452
453 int Directory::create(const std::string &path)
454 {
455     return create(path.c_str());
456 }
457
458
459 bool Directory::exists(const char *path)
460 {
461     struct stat info;
462     if (stat(path, &info) != 0)
463     {
464         if (errno != ENOENT && errno != ENOTDIR)
465         {
466             // TODO: Proper error handling.
467         }
468         return false;
469     }
470 #ifdef GMX_NATIVE_WINDOWS
471     return ((_S_IFDIR & info.st_mode) != 0);
472 #else
473     return S_ISDIR(info.st_mode);
474 #endif
475 }
476
477
478 bool Directory::exists(const std::string &path)
479 {
480     return exists(path.c_str());
481 }
482
483 } // namespace gmx