Throw on failure in gmx_file_rename
[alexxy/gromacs.git] / src / gromacs / utility / futil.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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, The GROMACS development team.
7  * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include "futil.h"
41
42 #include "config.h"
43
44 #include <cerrno>
45 #include <cstdio>
46 #include <cstdlib>
47 #include <cstring>
48
49 #include <mutex>
50 #include <tuple>
51
52 #include <fcntl.h>
53 #include <sys/stat.h>
54 #include <sys/types.h>
55
56 #ifdef HAVE_UNISTD_H
57 #    include <unistd.h>
58 #endif
59 #if GMX_NATIVE_WINDOWS
60 #    include <direct.h> // For _chdir() and _getcwd()
61 #    include <io.h>
62 #    include <windows.h>
63 #endif
64
65 #include "gromacs/utility/cstringutil.h"
66 #include "gromacs/utility/datafilefinder.h"
67 #include "gromacs/utility/dir_separator.h"
68 #include "gromacs/utility/exceptions.h"
69 #include "gromacs/utility/fatalerror.h"
70 #include "gromacs/utility/path.h"
71 #include "gromacs/utility/programcontext.h"
72 #include "gromacs/utility/smalloc.h"
73 #include "gromacs/utility/stringutil.h"
74
75 /* we keep a linked list of all files opened through pipes (i.e.
76    compressed or .gzipped files. This way we can distinguish between them
77    without having to change the semantics of reading from/writing to files)
78  */
79 typedef struct t_pstack
80 {
81     FILE*            fp;
82     struct t_pstack* prev;
83 } t_pstack;
84
85 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
86 static t_pstack* pstack = nullptr;
87 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
88 static bool bUnbuffered = false;
89 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
90 static int s_maxBackupCount = 0;
91
92 /* this linked list is an intrinsically globally shared object, so we have
93    to protect it with mutexes */
94 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
95 static std::mutex pstack_mutex;
96
97 using Lock = std::lock_guard<std::mutex>;
98
99 namespace gmx
100 {
101 namespace
102 {
103 //! Global library file finder; stores the object set with setLibraryFileFinder().
104 const DataFileFinder* g_libFileFinder; //NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
105 //! Default library file finder if nothing is set.
106 const DataFileFinder g_defaultLibFileFinder;
107 } // namespace
108
109 const DataFileFinder& getLibraryFileFinder()
110 {
111     if (g_libFileFinder != nullptr)
112     {
113         return *g_libFileFinder;
114     }
115     return g_defaultLibFileFinder;
116 }
117
118 void setLibraryFileFinder(const DataFileFinder* finder)
119 {
120     g_libFileFinder = finder;
121 }
122
123 } // namespace gmx
124
125 void gmx_disable_file_buffering()
126 {
127     bUnbuffered = true;
128 }
129
130 void gmx_set_max_backup_count(int count)
131 {
132     if (count < 0)
133     {
134         const char* env = getenv("GMX_MAXBACKUP");
135         if (env != nullptr)
136         {
137             // TODO: Check that the value is converted properly.
138             count = strtol(env, nullptr, 10);
139             if (count < 0)
140             {
141                 count = 0;
142             }
143         }
144         else
145         {
146             // Use a reasonably low value for countmax; we might
147             // generate 4-5 files in each round, and we don't
148             // want to hit directory limits of 1024 or 2048 files.
149             count = 99;
150         }
151     }
152     s_maxBackupCount = count;
153 }
154
155 static void push_ps(FILE* fp)
156 {
157     t_pstack* ps = nullptr;
158
159     Lock pstackLock(pstack_mutex);
160
161     snew(ps, 1);
162     ps->fp   = fp;
163     ps->prev = pstack;
164     pstack   = ps;
165 }
166
167 #if GMX_FAHCORE
168 #    ifdef gmx_ffclose
169 #        undef gmx_ffclose
170 #    endif
171 #endif
172 #if (!HAVE_PIPES && !defined(__native_client__))
173 static FILE* popen(const char* /* nm */, const char* /* mode */)
174 {
175     gmx_impl("Sorry no pipes...");
176
177     return NULL;
178 }
179
180 static int pclose(FILE* /* fp */)
181 {
182     gmx_impl("Sorry no pipes...");
183
184     return 0;
185 }
186 #endif /* !HAVE_PIPES && !defined(__native_client__) */
187
188 int gmx_ffclose(FILE* fp)
189 {
190     int ret = 0;
191
192     Lock pstackLock(pstack_mutex);
193
194     t_pstack* ps = pstack;
195     if (ps == nullptr)
196     {
197         if (fp != nullptr)
198         {
199             ret = fclose(fp);
200         }
201     }
202     else if (ps->fp == fp)
203     {
204         if (fp != nullptr)
205         {
206             ret = pclose(fp);
207         }
208         pstack = pstack->prev;
209         sfree(ps);
210     }
211     else
212     {
213         while ((ps->prev != nullptr) && (ps->prev->fp != fp))
214         {
215             ps = ps->prev;
216         }
217         if ((ps->prev != nullptr) && ps->prev->fp == fp)
218         {
219             if (ps->prev->fp != nullptr)
220             {
221                 ret = pclose(ps->prev->fp);
222             }
223             t_pstack* tmp = ps->prev;
224             ps->prev      = ps->prev->prev;
225             sfree(tmp);
226         }
227         else
228         {
229             if (fp != nullptr)
230             {
231                 ret = fclose(fp);
232             }
233         }
234     }
235
236     return ret;
237 }
238
239
240 void frewind(FILE* fp)
241 {
242     Lock pstackLock(pstack_mutex);
243
244     t_pstack* ps = pstack;
245     while (ps != nullptr)
246     {
247         if (ps->fp == fp)
248         {
249             fprintf(stderr, "Cannot rewind compressed file!\n");
250             return;
251         }
252         ps = ps->prev;
253     }
254     rewind(fp);
255 }
256
257 int gmx_fseek(FILE* stream, gmx_off_t offset, int whence)
258 {
259 #if HAVE_FSEEKO
260     return fseeko(stream, offset, whence);
261 #else
262 #    if HAVE__FSEEKI64
263     return _fseeki64(stream, offset, whence);
264 #    else
265     return fseek(stream, offset, whence);
266 #    endif
267 #endif
268 }
269
270 gmx_off_t gmx_ftell(FILE* stream)
271 {
272 #if HAVE_FSEEKO
273     return ftello(stream);
274 #else
275 #    if HAVE__FSEEKI64
276 #        ifndef __MINGW32__
277     return _ftelli64(stream);
278 #        else
279     return ftello64(stream);
280 #        endif
281 #    else
282     return ftell(stream);
283 #    endif
284 #endif
285 }
286
287 int gmx_truncate(const std::string& filename, gmx_off_t length)
288 {
289 #if GMX_NATIVE_WINDOWS && !GMX_FAHCORE
290     FILE* fp = fopen(filename.c_str(), "rb+");
291     if (fp == NULL)
292     {
293         return -1;
294     }
295 #    ifdef _MSC_VER
296     int rc = _chsize_s(fileno(fp), length);
297 #    else
298     int rc = _chsize(fileno(fp), length);
299 #    endif
300     fclose(fp);
301     return rc;
302 #else
303     return truncate(filename.c_str(), length);
304 #endif
305 }
306
307 static FILE* uncompress(const std::string& fn, const char* mode)
308 {
309     FILE*       fp  = nullptr;
310     std::string buf = "uncompress -c < " + fn;
311     fprintf(stderr, "Going to execute '%s'\n", buf.c_str());
312     if ((fp = popen(buf.c_str(), mode)) == nullptr)
313     {
314         gmx_open(fn);
315     }
316     push_ps(fp);
317
318     return fp;
319 }
320
321 static FILE* gunzip(const std::string& fn, const char* mode)
322 {
323     FILE*       fp  = nullptr;
324     std::string buf = "gunzip -c < ";
325     buf += fn;
326     fprintf(stderr, "Going to execute '%s'\n", buf.c_str());
327     if ((fp = popen(buf.c_str(), mode)) == nullptr)
328     {
329         gmx_open(fn);
330     }
331     push_ps(fp);
332
333     return fp;
334 }
335
336 gmx_bool gmx_fexist(const std::string& fname)
337 {
338     if (fname.empty())
339     {
340         return FALSE;
341     }
342     FILE* test = fopen(fname.c_str(), "r");
343     if (test == nullptr)
344     {
345 /*Windows doesn't allow fopen of directory - so we need to check this separately */
346 #if GMX_NATIVE_WINDOWS
347         DWORD attr = GetFileAttributes(fname.c_str());
348         return (attr != INVALID_FILE_ATTRIBUTES) && (attr & FILE_ATTRIBUTE_DIRECTORY);
349 #else
350         return FALSE;
351 #endif
352     }
353     else
354     {
355         fclose(test);
356         return TRUE;
357     }
358 }
359
360 static std::string backup_fn(const std::string& file)
361 {
362     int count = 1;
363
364     std::string directory = gmx::Path::getParentPath(file);
365     std::string fn        = gmx::Path::getFilename(file);
366     std::string buf;
367     if (directory.empty())
368     {
369         directory = ".";
370     }
371     do
372     {
373         buf = gmx::formatString("%s/#%s.%d#", directory.c_str(), fn.c_str(), count);
374         count++;
375     } while ((count <= s_maxBackupCount) && gmx_fexist(buf));
376
377     /* Arbitrarily bail out */
378     if (count > s_maxBackupCount)
379     {
380         /* TODO: The error message is only accurate for code that starts with
381          * Gromacs command-line interface. */
382         gmx_fatal(FARGS,
383                   "Won't make more than %d backups of %s for you.\n"
384                   "The env.var. GMX_MAXBACKUP controls this maximum, -1 disables backups.",
385                   s_maxBackupCount,
386                   fn.c_str());
387     }
388
389     return buf;
390 }
391
392 void make_backup(const std::string& name)
393 {
394     if (s_maxBackupCount <= 0)
395     {
396         return;
397     }
398     if (gmx_fexist(name))
399     {
400         auto backup = backup_fn(name);
401         if (rename(name.c_str(), backup.c_str()) == 0)
402         {
403             fprintf(stderr, "\nBack Off! I just backed up %s to %s\n", name.c_str(), backup.c_str());
404         }
405         else
406         {
407             fprintf(stderr, "\nSorry couldn't backup %s to %s\n", name.c_str(), backup.c_str());
408         }
409     }
410 }
411
412 FILE* gmx_ffopen(const std::string& file, const char* mode)
413 {
414     FILE* ff = nullptr;
415
416     if (file.empty())
417     {
418         return nullptr;
419     }
420
421     if (mode[0] == 'w')
422     {
423         make_backup(file);
424     }
425
426     bool bRead = (mode[0] == 'r' && mode[1] != '+');
427     if (!bRead || gmx_fexist(file))
428     {
429         if ((ff = fopen(file.c_str(), mode)) == nullptr)
430         {
431             gmx_file(file);
432         }
433         /* Check whether we should be using buffering (default) or not
434          * (for debugging)
435          */
436         const char* bufsize = nullptr;
437         if (bUnbuffered || ((bufsize = getenv("GMX_LOG_BUFFER")) != nullptr))
438         {
439             /* Check whether to use completely unbuffered */
440             const int bs = bUnbuffered ? 0 : strtol(bufsize, nullptr, 10);
441             if (bs <= 0)
442             {
443                 setbuf(ff, nullptr);
444             }
445             else
446             {
447                 char* ptr = nullptr;
448                 snew(ptr, bs + 8);
449                 if (setvbuf(ff, ptr, _IOFBF, bs) != 0)
450                 {
451                     gmx_file("Buffering File");
452                 }
453             }
454         }
455     }
456     else
457     {
458         std::string compressedFileName = file;
459         compressedFileName += ".Z";
460         if (gmx_fexist(compressedFileName))
461         {
462             ff = uncompress(compressedFileName, mode);
463         }
464         else
465         {
466             compressedFileName = file;
467             compressedFileName += ".gz";
468             if (gmx_fexist(compressedFileName))
469             {
470                 ff = gunzip(compressedFileName, mode);
471             }
472             else
473             {
474                 gmx_file(file);
475             }
476         }
477     }
478     return ff;
479 }
480
481 namespace gmx
482 {
483
484 std::string findLibraryFile(const std::string& filename, bool bAddCWD, bool bFatal)
485 {
486     std::string result;
487     try
488     {
489         const DataFileFinder& finder = getLibraryFileFinder();
490         result                       = finder.findFile(
491                 DataFileOptions(filename).includeCurrentDir(bAddCWD).throwIfNotFound(bFatal));
492     }
493     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
494     return result;
495 }
496
497 std::string findLibraryFile(const char* filename, bool bAddCWD, bool bFatal)
498 {
499     return findLibraryFile(std::string(filename), bAddCWD, bFatal);
500 }
501
502 FilePtr openLibraryFile(const std::string& filename, bool bAddCWD, bool bFatal)
503 {
504     FilePtr fp;
505     try
506     {
507         const DataFileFinder& finder = getLibraryFileFinder();
508         fp = finder.openFile(DataFileOptions(filename).includeCurrentDir(bAddCWD).throwIfNotFound(bFatal));
509     }
510     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
511     return fp;
512 }
513
514 FilePtr openLibraryFile(const char* filename, bool bAddCWD, bool bFatal)
515 {
516     return openLibraryFile(std::string(filename), bAddCWD, bFatal);
517 }
518
519 } // namespace gmx
520
521 /*! \brief Use mkstemp (or similar function to make a new temporary
522  * file and (on non-Windows systems) return a file descriptor to it.
523  *
524  * Note: not thread-safe on non-Windows systems
525  *
526  * \todo Use std::string and std::vector<char>. */
527 static int makeTemporaryFilename(char* buf)
528 {
529     int len = 0;
530
531     if ((len = strlen(buf)) < 7)
532     {
533         gmx_fatal(FARGS, "Buf passed to gmx_tmpnam must be at least 7 bytes long");
534     }
535     for (int i = len - 6; (i < len); i++)
536     {
537         buf[i] = 'X';
538     }
539     /* mktemp is dangerous and we should use mkstemp instead, but
540      * since windows doesnt support it we have to separate the cases.
541      * 20090307: mktemp deprecated, use iso c++ _mktemp instead.
542      */
543 #if GMX_NATIVE_WINDOWS
544     _mktemp(buf);
545     if (buf == NULL)
546     {
547         gmx_fatal(FARGS, "Error creating temporary file %s: %s", buf, strerror(errno));
548     }
549     int fd = 0;
550 #else
551     int fd = mkstemp(buf);
552
553     /* mkstemp creates 0600 files - respect umask instead */
554     mode_t currUmask = umask(0);
555     umask(currUmask);
556     fchmod(fd, 0666 & ~currUmask);
557
558     if (fd < 0)
559     {
560         gmx_fatal(FARGS, "Error creating temporary file %s: %s", buf, strerror(errno));
561     }
562 #endif
563     return fd;
564 }
565 // TODO use std::string
566 void gmx_tmpnam(char* buf)
567 {
568     int fd = makeTemporaryFilename(buf);
569 #if !GMX_NATIVE_WINDOWS
570     close(fd);
571 #endif
572 }
573
574 // TODO use std::string
575 FILE* gmx_fopen_temporary(char* buf)
576 {
577     FILE* fpout = nullptr;
578     int   fd    = makeTemporaryFilename(buf);
579
580 #if GMX_NATIVE_WINDOWS
581     if ((fpout = fopen(buf, "w")) == NULL)
582     {
583         gmx_fatal(FARGS, "Cannot open temporary file %s", buf);
584     }
585 #else
586     if ((fpout = fdopen(fd, "w")) == nullptr)
587     {
588         gmx_fatal(FARGS, "Cannot open temporary file %s", buf);
589     }
590 #endif
591
592     return fpout;
593 }
594
595 void gmx_file_rename(const char* oldname, const char* newname)
596 {
597     int code;
598 #if !GMX_NATIVE_WINDOWS
599     /* under unix, rename() is atomic (at least, it should be). */
600     code = rename(oldname, newname);
601 #else
602     if (MoveFileEx(oldname, newname, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
603     {
604 #    if GMX_FAHCORE
605         /* This just lets the F@H checksumming system know about the rename */
606         fcRename(oldname, newname);
607 #    endif
608         code = 0;
609     }
610     else
611     {
612         code = 1;
613     }
614 #endif
615     if (code != 0)
616     {
617         auto errorMsg = gmx::formatString("Failed to rename %s to %s.", oldname, newname);
618         GMX_THROW(gmx::FileIOError(errorMsg));
619     }
620 }
621
622 int gmx_file_copy(const char* oldname, const char* newname, gmx_bool copy_if_empty)
623 {
624     gmx::FilePtr in(fopen(oldname, "rb"));
625     if (!in)
626     {
627         return 1;
628     }
629
630     /* If we don't copy when empty, we postpone opening the file
631        until we're actually ready to write. */
632     gmx::FilePtr out;
633     if (copy_if_empty)
634     {
635         out.reset(fopen(newname, "wb"));
636         if (!out)
637         {
638             return 1;
639         }
640     }
641
642     /* the full copy buffer size: */
643     constexpr int     FILECOPY_BUFSIZE = 1 << 16;
644     std::vector<char> buf(FILECOPY_BUFSIZE);
645
646     while (!feof(in.get()))
647     {
648         size_t nread = fread(buf.data(), sizeof(char), FILECOPY_BUFSIZE, in.get());
649         if (nread > 0)
650         {
651             size_t ret = 0;
652             if (!out)
653             {
654                 /* so this is where we open when copy_if_empty is false:
655                    here we know we read something. */
656                 out.reset(fopen(newname, "wb"));
657                 if (!out)
658                 {
659                     return 1;
660                 }
661             }
662             ret = fwrite(buf.data(), sizeof(char), nread, out.get());
663             if (ret != nread)
664             {
665                 return 1;
666             }
667         }
668         if (ferror(in.get()))
669         {
670             return 1;
671         }
672     }
673     return 0;
674 }
675
676
677 int gmx_fsync(FILE* fp)
678 {
679     int rc = 0;
680
681     {
682         /* get the file number */
683 #if HAVE_FILENO
684         int fn = fileno(fp);
685 #elif HAVE__FILENO
686         int fn = _fileno(fp);
687 #else
688         GMX_UNUSED_VALUE(fp);
689         int fn = -1;
690 #endif
691
692         /* do the actual fsync */
693         if (fn >= 0)
694         {
695 #if HAVE_FSYNC
696             rc = fsync(fn);
697 #elif HAVE__COMMIT
698             rc = _commit(fn);
699 #endif
700         }
701     }
702
703     /* We check for these error codes this way because POSIX requires them
704        to be defined, and using anything other than macros is unlikely: */
705 #ifdef EINTR
706     /* we don't want to report an error just because fsync() caught a signal.
707        For our purposes, we can just ignore this. */
708     if (rc && errno == EINTR)
709     {
710         rc = 0;
711     }
712 #endif
713 #ifdef EINVAL
714     /* we don't want to report an error just because we tried to fsync()
715        stdout, a socket or a pipe. */
716     if (rc && errno == EINVAL)
717     {
718         rc = 0;
719     }
720 #endif
721     return rc;
722 }
723
724 void gmx_chdir(const char* directory)
725 {
726 #if GMX_NATIVE_WINDOWS
727     int rc = _chdir(directory);
728 #else
729     int   rc   = chdir(directory);
730 #endif
731     if (rc != 0)
732     {
733         auto message = gmx::formatString(
734                 "Cannot change directory to '%s'. Reason: %s", directory, strerror(errno));
735         GMX_THROW(gmx::FileIOError(message));
736     }
737 }
738
739 void gmx_getcwd(char* buffer, size_t size)
740 {
741 #if GMX_NATIVE_WINDOWS
742     char* pdum = _getcwd(buffer, size);
743 #else
744     char* pdum = getcwd(buffer, size);
745 #endif
746     if (pdum == nullptr)
747     {
748         gmx_fatal(FARGS, "Cannot get working directory. Reason: %s", strerror(errno));
749     }
750 }