2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5 * Copyright (c) 2001-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016,2017,2018,2019,2020, by the GROMACS development team, led by
7 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8 * and including many others, as listed in the AUTHORS file in the
9 * top-level source directory and at http://www.gromacs.org.
11 * GROMACS is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * as published by the Free Software Foundation; either version 2.1
14 * of the License, or (at your option) any later version.
16 * GROMACS is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with GROMACS; if not, see
23 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 * If you want to redistribute modifications to GROMACS, please
27 * consider that scientific software is very special. Version
28 * control is crucial - bugs must be traceable. We will be happy to
29 * consider code for inclusion in the official distribution, but
30 * derived work must not be called official GROMACS. Details are found
31 * in the README & COPYING files - if they are missing, get the
32 * official version at http://www.gromacs.org.
34 * To help us fund GROMACS development, we humbly ask that you cite
35 * the research papers on the package. Check out http://www.gromacs.org.
52 #include <sys/types.h>
57 #if GMX_NATIVE_WINDOWS
58 # include <direct.h> // For _chdir() and _getcwd()
63 #include "gromacs/utility/cstringutil.h"
64 #include "gromacs/utility/datafilefinder.h"
65 #include "gromacs/utility/dir_separator.h"
66 #include "gromacs/utility/exceptions.h"
67 #include "gromacs/utility/fatalerror.h"
68 #include "gromacs/utility/mutex.h"
69 #include "gromacs/utility/path.h"
70 #include "gromacs/utility/programcontext.h"
71 #include "gromacs/utility/smalloc.h"
72 #include "gromacs/utility/stringutil.h"
74 /* we keep a linked list of all files opened through pipes (i.e.
75 compressed or .gzipped files. This way we can distinguish between them
76 without having to change the semantics of reading from/writing to files)
78 typedef struct t_pstack
81 struct t_pstack* prev;
84 static t_pstack* pstack = nullptr;
85 static bool bUnbuffered = false;
86 static int s_maxBackupCount = 0;
88 /* this linked list is an intrinsically globally shared object, so we have
89 to protect it with mutexes */
90 static gmx::Mutex pstack_mutex;
92 using Lock = gmx::lock_guard<gmx::Mutex>;
98 //! Global library file finder; stores the object set with setLibraryFileFinder().
99 const DataFileFinder* g_libFileFinder;
100 //! Default library file finder if nothing is set.
101 const DataFileFinder g_defaultLibFileFinder;
104 const DataFileFinder& getLibraryFileFinder()
106 if (g_libFileFinder != nullptr)
108 return *g_libFileFinder;
110 return g_defaultLibFileFinder;
113 void setLibraryFileFinder(const DataFileFinder* finder)
115 g_libFileFinder = finder;
120 void gmx_disable_file_buffering()
125 void gmx_set_max_backup_count(int count)
129 const char* env = getenv("GMX_MAXBACKUP");
132 // TODO: Check that the value is converted properly.
133 count = strtol(env, nullptr, 10);
141 // Use a reasonably low value for countmax; we might
142 // generate 4-5 files in each round, and we don't
143 // want to hit directory limits of 1024 or 2048 files.
147 s_maxBackupCount = count;
150 static void push_ps(FILE* fp)
154 Lock pstackLock(pstack_mutex);
167 #if (!HAVE_PIPES && !defined(__native_client__))
168 static FILE* popen(const char* nm, const char* mode)
170 gmx_impl("Sorry no pipes...");
175 static int pclose(FILE* fp)
177 gmx_impl("Sorry no pipes...");
181 #endif /* !HAVE_PIPES && !defined(__native_client__) */
183 int gmx_ffclose(FILE* fp)
188 Lock pstackLock(pstack_mutex);
198 else if (ps->fp == fp)
204 pstack = pstack->prev;
209 while ((ps->prev != nullptr) && (ps->prev->fp != fp))
213 if ((ps->prev != nullptr) && ps->prev->fp == fp)
215 if (ps->prev->fp != nullptr)
217 ret = pclose(ps->prev->fp);
220 ps->prev = ps->prev->prev;
236 void frewind(FILE* fp)
238 Lock pstackLock(pstack_mutex);
240 t_pstack* ps = pstack;
241 while (ps != nullptr)
245 fprintf(stderr, "Cannot rewind compressed file!\n");
253 int gmx_fseek(FILE* stream, gmx_off_t offset, int whence)
256 return fseeko(stream, offset, whence);
259 return _fseeki64(stream, offset, whence);
261 return fseek(stream, offset, whence);
266 gmx_off_t gmx_ftell(FILE* stream)
269 return ftello(stream);
273 return _ftelli64(stream);
275 return ftello64(stream);
278 return ftell(stream);
283 int gmx_truncate(const std::string& filename, gmx_off_t length)
285 #if GMX_NATIVE_WINDOWS && !GMX_FAHCORE
286 FILE* fp = fopen(filename.c_str(), "rb+");
292 int rc = _chsize_s(fileno(fp), length);
294 int rc = _chsize(fileno(fp), length);
299 return truncate(filename.c_str(), length);
303 static FILE* uncompress(const std::string& fn, const char* mode)
306 std::string buf = "uncompress -c < " + fn;
307 fprintf(stderr, "Going to execute '%s'\n", buf.c_str());
308 if ((fp = popen(buf.c_str(), mode)) == nullptr)
317 static FILE* gunzip(const std::string& fn, const char* mode)
320 std::string buf = "gunzip -c < ";
322 fprintf(stderr, "Going to execute '%s'\n", buf.c_str());
323 if ((fp = popen(buf.c_str(), mode)) == nullptr)
332 gmx_bool gmx_fexist(const std::string& fname)
340 test = fopen(fname.c_str(), "r");
343 /*Windows doesn't allow fopen of directory - so we need to check this seperately */
344 #if GMX_NATIVE_WINDOWS
345 DWORD attr = GetFileAttributes(fname.c_str());
346 return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
358 static std::string backup_fn(const std::string& file)
362 std::string directory = gmx::Path::getParentPath(file);
363 std::string fn = gmx::Path::getFilename(file);
365 if (directory.empty())
371 buf = gmx::formatString("%s/#%s.%d#", directory.c_str(), fn.c_str(), count);
373 } while ((count <= s_maxBackupCount) && gmx_fexist(buf));
375 /* Arbitrarily bail out */
376 if (count > s_maxBackupCount)
378 /* TODO: The error message is only accurate for code that starts with
379 * Gromacs command-line interface. */
381 "Won't make more than %d backups of %s for you.\n"
382 "The env.var. GMX_MAXBACKUP controls this maximum, -1 disables backups.",
383 s_maxBackupCount, fn.c_str());
389 void make_backup(const std::string& name)
391 if (s_maxBackupCount <= 0)
395 if (gmx_fexist(name))
397 auto backup = backup_fn(name);
398 if (rename(name.c_str(), backup.c_str()) == 0)
400 fprintf(stderr, "\nBack Off! I just backed up %s to %s\n", name.c_str(), backup.c_str());
404 fprintf(stderr, "\nSorry couldn't backup %s to %s\n", name.c_str(), backup.c_str());
409 FILE* gmx_ffopen(const std::string& file, const char* mode)
425 bRead = (mode[0] == 'r' && mode[1] != '+');
426 if (!bRead || gmx_fexist(file))
428 if ((ff = fopen(file.c_str(), mode)) == nullptr)
432 /* Check whether we should be using buffering (default) or not
435 const char* bufsize = nullptr;
436 if (bUnbuffered || ((bufsize = getenv("GMX_LOG_BUFFER")) != nullptr))
438 /* Check whether to use completely unbuffered */
445 bs = strtol(bufsize, nullptr, 10);
455 if (setvbuf(ff, ptr, _IOFBF, bs) != 0)
457 gmx_file("Buffering File");
464 std::string compressedFileName = file;
465 compressedFileName += ".Z";
466 if (gmx_fexist(compressedFileName))
468 ff = uncompress(compressedFileName, mode);
472 compressedFileName = file;
473 compressedFileName += ".gz";
474 if (gmx_fexist(compressedFileName))
476 ff = gunzip(compressedFileName, mode);
490 std::string findLibraryFile(const std::string& filename, bool bAddCWD, bool bFatal)
495 const DataFileFinder& finder = getLibraryFileFinder();
496 result = finder.findFile(
497 DataFileOptions(filename).includeCurrentDir(bAddCWD).throwIfNotFound(bFatal));
499 GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
503 std::string findLibraryFile(const char* filename, bool bAddCWD, bool bFatal)
505 return findLibraryFile(std::string(filename), bAddCWD, bFatal);
508 FilePtr openLibraryFile(const std::string& filename, bool bAddCWD, bool bFatal)
513 const DataFileFinder& finder = getLibraryFileFinder();
514 fp = finder.openFile(DataFileOptions(filename).includeCurrentDir(bAddCWD).throwIfNotFound(bFatal));
516 GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
520 FilePtr openLibraryFile(const char* filename, bool bAddCWD, bool bFatal)
522 return openLibraryFile(std::string(filename), bAddCWD, bFatal);
527 /*! \brief Use mkstemp (or similar function to make a new temporary
528 * file and (on non-Windows systems) return a file descriptor to it.
530 * \todo Use std::string and std::vector<char>. */
531 static int makeTemporaryFilename(char* buf)
535 if ((len = strlen(buf)) < 7)
537 gmx_fatal(FARGS, "Buf passed to gmx_tmpnam must be at least 7 bytes long");
539 for (int i = len - 6; (i < len); i++)
543 /* mktemp is dangerous and we should use mkstemp instead, but
544 * since windows doesnt support it we have to separate the cases.
545 * 20090307: mktemp deprecated, use iso c++ _mktemp instead.
548 #if GMX_NATIVE_WINDOWS
552 gmx_fatal(FARGS, "Error creating temporary file %s: %s", buf, strerror(errno));
560 gmx_fatal(FARGS, "Error creating temporary file %s: %s", buf, strerror(errno));
565 // TODO use std::string
566 void gmx_tmpnam(char* buf)
568 int fd = makeTemporaryFilename(buf);
569 #if !GMX_NATIVE_WINDOWS
574 // TODO use std::string
575 FILE* gmx_fopen_temporary(char* buf)
577 FILE* fpout = nullptr;
578 int fd = makeTemporaryFilename(buf);
580 #if GMX_NATIVE_WINDOWS
581 if ((fpout = fopen(buf, "w")) == NULL)
583 gmx_fatal(FARGS, "Cannot open temporary file %s", buf);
586 if ((fpout = fdopen(fd, "w")) == nullptr)
588 gmx_fatal(FARGS, "Cannot open temporary file %s", buf);
595 int gmx_file_rename(const char* oldname, const char* newname)
597 #if !GMX_NATIVE_WINDOWS
598 /* under unix, rename() is atomic (at least, it should be). */
599 return rename(oldname, newname);
601 if (MoveFileEx(oldname, newname, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
604 /* This just lets the F@H checksumming system know about the rename */
605 fcRename(oldname, newname);
616 int gmx_file_copy(const char* oldname, const char* newname, gmx_bool copy_if_empty)
618 gmx::FilePtr in(fopen(oldname, "rb"));
624 /* If we don't copy when empty, we postpone opening the file
625 until we're actually ready to write. */
629 out.reset(fopen(newname, "wb"));
636 /* the full copy buffer size: */
637 constexpr int FILECOPY_BUFSIZE = 1 << 16;
638 std::vector<char> buf(FILECOPY_BUFSIZE);
640 while (!feof(in.get()))
644 nread = fread(buf.data(), sizeof(char), FILECOPY_BUFSIZE, in.get());
650 /* so this is where we open when copy_if_empty is false:
651 here we know we read something. */
652 out.reset(fopen(newname, "wb"));
658 ret = fwrite(buf.data(), sizeof(char), nread, out.get());
664 if (ferror(in.get()))
673 int gmx_fsync(FILE* fp)
680 /* get the file number */
689 /* do the actual fsync */
700 /* We check for these error codes this way because POSIX requires them
701 to be defined, and using anything other than macros is unlikely: */
703 /* we don't want to report an error just because fsync() caught a signal.
704 For our purposes, we can just ignore this. */
705 if (rc && errno == EINTR)
711 /* we don't want to report an error just because we tried to fsync()
712 stdout, a socket or a pipe. */
713 if (rc && errno == EINVAL)
721 void gmx_chdir(const char* directory)
723 #if GMX_NATIVE_WINDOWS
724 int rc = _chdir(directory);
726 int rc = chdir(directory);
730 gmx_fatal(FARGS, "Cannot change directory to '%s'. Reason: %s", directory, strerror(errno));
734 void gmx_getcwd(char* buffer, size_t size)
736 #if GMX_NATIVE_WINDOWS
737 char* pdum = _getcwd(buffer, size);
739 char* pdum = getcwd(buffer, size);
743 gmx_fatal(FARGS, "Cannot get working directory. Reason: %s", strerror(errno));