LCOV - code coverage report
Current view: top level - tools - OFile.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 249 271 91.9 %
Date: 2026-11-12 10:48:31 Functions: 29 31 93.5 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed 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
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "OFile.h"
      23             : #include "Exception.h"
      24             : #include "core/Action.h"
      25             : #include "core/PlumedMain.h"
      26             : #include "core/Value.h"
      27             : #include "Communicator.h"
      28             : #include "Tools.h"
      29             : #include <cstdarg>
      30             : #include <cstring>
      31             : 
      32             : #include <iostream>
      33             : #include <string>
      34             : #include <cstdlib>
      35             : #include <cerrno>
      36             : 
      37             : #include <memory>
      38             : #include <utility>
      39             : 
      40             : #include <sys/types.h>
      41             : #include <sys/stat.h>
      42             : 
      43             : #ifdef __PLUMED_HAS_ZLIB
      44             : #include <zlib.h>
      45             : #endif
      46             : 
      47             : namespace PLMD {
      48             : 
      49     6129407 : size_t OFile::llwrite(const char*ptr,size_t s) {
      50             :   size_t r;
      51     6129407 :   if(linked) {
      52          52 :     return linked->llwrite(ptr,s);
      53             :   }
      54     6129355 :   if(! (comm && comm->Get_rank()>0)) {
      55     5240663 :     if(!fp) {
      56           0 :       plumed_merror("writing on uninitialized File");
      57             :     }
      58     5240663 :     if(gzfp) {
      59             : #ifdef __PLUMED_HAS_ZLIB
      60        1263 :       r=gzwrite(gzFile(gzfp),ptr,s);
      61             : #else
      62             :       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
      63             : #endif
      64             :     } else {
      65     5239400 :       r=std::fwrite(ptr,1,s,fp);
      66             :     }
      67             :   }
      68     6129355 :   if(comm) {
      69             : //  This barrier is apparently useless since it comes
      70             : //  just before a Bcast.
      71             : //
      72             : //  Anyway, it looks like it is solving an issue that appeared on
      73             : //  TRAVIS (at least on my laptop) so I add it here.
      74             : //  GB
      75     5266815 :     comm->Barrier();
      76     5266815 :     comm->Bcast(r,0);
      77             :   }
      78             : 
      79     6129355 :   return r;
      80             : }
      81             : 
      82      823341 : OFile::OFile():
      83      823341 :   linked(NULL),
      84      823341 :   fieldChanged(false),
      85      823341 :   backstring("bck"),
      86      823341 :   enforceRestart_(false),
      87      823341 :   enforceBackup_(false) {
      88      823341 :   fmtField();
      89      823341 :   buflen=1;
      90      823341 :   actual_buffer_length=0;
      91      823341 :   buffer.resize(buflen);
      92             : // these are set to zero to avoid valgrind errors
      93     1646682 :   for(int i=0; i<buflen; ++i) {
      94      823341 :     buffer[i]=0;
      95             :   }
      96             : // these are set to zero to avoid valgrind errors
      97      823341 :   buffer_string.resize(1000,0);
      98      823341 : }
      99             : 
     100          11 : OFile& OFile::link(OFile&l) {
     101          11 :   fp=NULL;
     102          11 :   gzfp=NULL;
     103          11 :   linked=&l;
     104          11 :   return *this;
     105             : }
     106             : 
     107      807846 : OFile& OFile::setLinePrefix(const std::string&l) {
     108      807846 :   linePrefix=l;
     109      807846 :   return *this;
     110             : }
     111             : 
     112    25248471 : int OFile::printf(const char*fmt,...) {
     113             :   va_list arg;
     114    25248471 :   va_start(arg, fmt);
     115    25248471 :   int r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,arg);
     116    25248471 :   va_end(arg);
     117    25248471 :   if(r>=buflen-actual_buffer_length) {
     118             :     int newlen=buflen;
     119       62542 :     while(newlen<=r+actual_buffer_length) {
     120       41430 :       newlen*=2;
     121             :     }
     122       21112 :     std::vector<char> newbuf(newlen);
     123       21112 :     std::memmove(newbuf.data(),buffer.data(),buflen);
     124    14292516 :     for(int k=buflen; k<newlen; k++) {
     125    14271404 :       newbuf[k]=0;
     126             :     }
     127             :     std::swap(buffer,newbuf);
     128       21112 :     buflen=newlen;
     129             :     va_list arg;
     130       21112 :     va_start(arg, fmt);
     131       21112 :     r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,arg);
     132       21112 :     va_end(arg);
     133             :   }
     134    25248471 :   plumed_massert(r>-1 && r<buflen-actual_buffer_length,"error using fmt string " + std::string(fmt));
     135             : 
     136             : // Line is buffered until newline, then written with a PLUMED: prefix
     137             :   char*p1=buffer.data();
     138             :   char*p2;
     139             : // newline is only searched in the just added portion:
     140    25248471 :   char*psearch=p1+actual_buffer_length;
     141    25248471 :   actual_buffer_length+=r;
     142    30767487 :   while((p2=std::strchr(psearch,'\n'))) {
     143     5519016 :     if(linePrefix.length()>0) {
     144      610339 :       llwrite(linePrefix.c_str(),linePrefix.length());
     145             :     }
     146     5519016 :     llwrite(p1,p2-p1+1);
     147     5519016 :     actual_buffer_length-=(p2-p1)+1;
     148     5519016 :     p1=p2+1;
     149             :     psearch=p1;
     150             :   };
     151    25248471 :   if(buffer.data()!=p1) {
     152     5503888 :     std::memmove(buffer.data(),p1,actual_buffer_length);
     153             :   }
     154    25248471 :   return r;
     155             : }
     156             : 
     157       28138 : OFile& OFile::addConstantField(const std::string&name) {
     158             :   Field f;
     159             :   f.name=name;
     160       28138 :   const_fields.push_back(f);
     161       28138 :   return *this;
     162             : }
     163             : 
     164             : 
     165        3242 : OFile& OFile::clearFields() {
     166        3242 :   fields.clear();
     167        3242 :   const_fields.clear();
     168        3242 :   previous_fields.clear();
     169        3242 :   return *this;
     170             : }
     171             : 
     172    14321350 : OFile& OFile::fmtField(const std::string&fmt) {
     173    14321350 :   this->fieldFmt=fmt;
     174    14321350 :   return *this;
     175             : }
     176             : 
     177      829212 : OFile& OFile::fmtField() {
     178      829212 :   this->fieldFmt="%23.16lg";
     179      829212 :   return *this;
     180             : }
     181             : 
     182    15883281 : OFile& OFile::printField(const std::string&name,double v) {
     183             : // When one tries to print -nan we print nan instead.
     184             : // The distinction between +nan and -nan is not well defined
     185             : // Always printing nan simplifies some regtest (special functions computed our of range).
     186    15883281 :   if(std::isnan(v)) {
     187             :     v=std::numeric_limits<double>::quiet_NaN();
     188             :   }
     189             :   std::snprintf(buffer_string.data(),buffer_string.size(),fieldFmt.c_str(),v);
     190    15883281 :   printField(name,buffer_string.data());
     191    15883281 :   return *this;
     192             : }
     193             : 
     194     6150607 : OFile& OFile::printField(const std::string&name,int v) {
     195             :   std::snprintf(buffer_string.data(),buffer_string.size()," %d",v);
     196     6150607 :   printField(name,buffer_string.data());
     197     6150607 :   return *this;
     198             : }
     199             : 
     200           1 : OFile& OFile::printField(const std::string&name,long int v) {
     201             :   std::snprintf(buffer_string.data(),buffer_string.size()," %ld",v);
     202           1 :   printField(name,buffer_string.data());
     203           1 :   return *this;
     204             : }
     205             : 
     206           0 : OFile& OFile::printField(const std::string&name,long long int v) {
     207             :   std::snprintf(buffer_string.data(),buffer_string.size()," %lld",v);
     208           0 :   printField(name,buffer_string.data());
     209           0 :   return *this;
     210             : }
     211             : 
     212          37 : OFile& OFile::printField(const std::string&name,unsigned v) {
     213             :   std::snprintf(buffer_string.data(),buffer_string.size()," %u",v);
     214          37 :   printField(name,buffer_string.data());
     215          37 :   return *this;
     216             : }
     217             : 
     218           1 : OFile& OFile::printField(const std::string&name,long unsigned v) {
     219             :   std::snprintf(buffer_string.data(),buffer_string.size()," %lu",v);
     220           1 :   printField(name,buffer_string.data());
     221           1 :   return *this;
     222             : }
     223             : 
     224          55 : OFile& OFile::printField(const std::string&name,long long unsigned v) {
     225             :   std::snprintf(buffer_string.data(),buffer_string.size()," %llu",v);
     226          55 :   printField(name,buffer_string.data());
     227          55 :   return *this;
     228             : }
     229             : 
     230    38656408 : OFile& OFile::printField(const std::string&name,const std::string & v) {
     231             :   unsigned i;
     232   201910262 :   for(i=0; i<const_fields.size(); i++)
     233   185000100 :     if(const_fields[i].name==name) {
     234             :       break;
     235             :     }
     236    38656408 :   if(i>=const_fields.size()) {
     237             :     Field field;
     238             :     field.name=name;
     239             :     field.value=v;
     240    16910162 :     fields.push_back(field);
     241             :   } else {
     242    21746246 :     if(const_fields[i].value!=v) {
     243       28066 :       fieldChanged=true;
     244             :     }
     245             :     const_fields[i].value=v;
     246             :   }
     247    38656408 :   return *this;
     248             : }
     249             : 
     250        6985 : OFile& OFile::setupPrintValue( Value *val ) {
     251        6985 :   if( val->isPeriodic() ) {
     252         520 :     addConstantField("min_" + val->getName() );
     253        1040 :     addConstantField("max_" + val->getName() );
     254             :   }
     255        6985 :   return *this;
     256             : }
     257             : 
     258       32833 : OFile& OFile::printField( Value* val, const double& v ) {
     259       32833 :   printField( val->getName(), v );
     260       32833 :   if( val->isPeriodic() ) {
     261             :     std::string min, max;
     262        1660 :     val->getDomain( min, max );
     263        1660 :     printField( "min_" + val->getName(), min );
     264        3320 :     printField("max_" + val->getName(), max );
     265             :   }
     266       32833 :   return *this;
     267             : }
     268             : 
     269     3855299 : OFile& OFile::printField() {
     270             :   bool reprint=false;
     271     3855299 :   if(fieldChanged || fields.size()!=previous_fields.size()) {
     272             :     reprint=true;
     273             :   } else
     274    20169518 :     for(unsigned i=0; i<fields.size(); i++) {
     275    16320667 :       if( previous_fields[i].name!=fields[i].name ||
     276    16320665 :           (fields[i].constant && fields[i].value!=previous_fields[i].value) ) {
     277             :         reprint=true;
     278             :         break;
     279             :       }
     280             :     }
     281     3855299 :   if(reprint) {
     282        6448 :     printf("#! FIELDS");
     283      595947 :     for(unsigned i=0; i<fields.size(); i++) {
     284      589499 :       printf(" %s",fields[i].name.c_str());
     285             :     }
     286        6448 :     printf("\n");
     287       34518 :     for(unsigned i=0; i<const_fields.size(); i++) {
     288       28070 :       printf("#! SET %s %s",const_fields[i].name.c_str(),const_fields[i].value.c_str());
     289       28070 :       printf("\n");
     290             :     }
     291             :   }
     292    20765461 :   for(unsigned i=0; i<fields.size(); i++) {
     293    16910162 :     printf("%s",fields[i].value.c_str());
     294             :   }
     295     3855299 :   printf("\n");
     296     3855299 :   previous_fields=fields;
     297     3855299 :   fields.clear();
     298     3855299 :   fieldChanged=false;
     299     3855299 :   return *this;
     300             : }
     301             : 
     302         208 : void OFile::setBackupString( const std::string& str ) {
     303         208 :   backstring=str;
     304         208 : }
     305             : 
     306           0 : void OFile::backupAllFiles( const std::string& str ) {
     307           0 :   if(str=="/dev/null") {
     308           0 :     return;
     309             :   }
     310           0 :   plumed_assert( backstring!="bck" && !checkRestart());
     311           0 :   size_t found=str.find_last_of("/\\");
     312           0 :   std::string filename = appendSuffix(str,getSuffix());
     313           0 :   std::string directory=filename.substr(0,found+1);
     314           0 :   std::string file=filename.substr(found+1);
     315           0 :   if( FileExist(filename) ) {
     316           0 :     backupFile("bck", filename);
     317             :   }
     318           0 :   for(int i=0;; i++) {
     319             :     std::string num;
     320           0 :     Tools::convert(i,num);
     321           0 :     std::string filestr = directory + backstring + "." + num + "." + file;
     322           0 :     if( !FileExist(filestr) ) {
     323             :       break;
     324             :     }
     325           0 :     backupFile( "bck", filestr);
     326           0 :   }
     327             : }
     328             : 
     329        4035 : void OFile::backupFile( const std::string& bstring, const std::string& fname ) {
     330        4035 :   if(fname=="/dev/null") {
     331         277 :     return;
     332             :   }
     333        3758 :   int maxbackup=100;
     334        3758 :   if(std::getenv("PLUMED_MAXBACKUP")) {
     335          78 :     Tools::convert(std::getenv("PLUMED_MAXBACKUP"),maxbackup);
     336             :   }
     337        3758 :   if(maxbackup>0 && (!comm || comm->Get_rank()==0)) {
     338        3175 :     FILE* ff=std::fopen(const_cast<char*>(fname.c_str()),"r");
     339        3175 :     if(ff) {
     340             :       // no exception here
     341         124 :       std::fclose(ff);
     342             :       std::string backup;
     343         124 :       size_t found=fname.find_last_of("/\\");
     344         124 :       std::string directory=fname.substr(0,found+1);
     345         124 :       std::string file=fname.substr(found+1);
     346         124 :       for(int i=0;; i++) {
     347             :         std::string num;
     348         182 :         Tools::convert(i,num);
     349         182 :         if(i>maxbackup) {
     350           0 :           plumed_merror("cannot backup file "+file+" maximum number of backup is "+num+"\n");
     351             :         }
     352         364 :         backup=directory+bstring +"."+num+"."+file;
     353         182 :         FILE* fff=std::fopen(backup.c_str(),"r");
     354             :         // no exception here
     355         182 :         if(!fff) {
     356             :           break;
     357             :         } else {
     358          58 :           std::fclose(fff);
     359             :         }
     360          58 :       }
     361         124 :       int check=rename(fname.c_str(),backup.c_str());
     362         124 :       plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+std::strerror(errno));
     363             :     }
     364             :   }
     365             : }
     366             : 
     367        4433 : OFile& OFile::open(const std::string&path) {
     368        4433 :   plumed_assert(!cloned);
     369        4433 :   eof=false;
     370        4433 :   err=false;
     371        4433 :   fp=NULL;
     372        4433 :   gzfp=NULL;
     373        4433 :   this->path=path;
     374        8866 :   this->path=appendSuffix(path,getSuffix());
     375             :   // this must be done before backing up the file, otherwise
     376             :   // the backup would rename the file that is being written by another OFile
     377        4433 :   checkNotOpenElsewhere();
     378        4428 :   if(checkRestart()) {
     379         393 :     fp=std::fopen(const_cast<char*>(this->path.c_str()),"a");
     380         393 :     mode="a";
     381         786 :     if(Tools::extension(this->path)=="gz") {
     382             : #ifdef __PLUMED_HAS_ZLIB
     383          12 :       gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"a9");
     384             : #else
     385             :       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
     386             : #endif
     387             :     }
     388             :   } else {
     389        4035 :     backupFile( backstring, this->path );
     390        4035 :     if(comm) {
     391        3992 :       comm->Barrier();
     392             :     }
     393        4035 :     fp=std::fopen(const_cast<char*>(this->path.c_str()),"w");
     394        4035 :     mode="w";
     395        8070 :     if(Tools::extension(this->path)=="gz") {
     396             : #ifdef __PLUMED_HAS_ZLIB
     397          13 :       gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"w9");
     398             : #else
     399             :       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
     400             : #endif
     401             :     }
     402             :   }
     403        4428 :   if(plumed) {
     404        3962 :     plumed->insertFile(*this);
     405             :   }
     406        4428 :   return *this;
     407             : }
     408             : 
     409        4433 : void OFile::checkNotOpenElsewhere()const {
     410        4433 :   if(!plumed) {
     411         648 :     return;
     412             :   }
     413        3967 :   if(path=="/dev/null") {
     414             :     return;
     415             :   }
     416             :   struct stat st;
     417        3785 :   const bool exists=(stat(path.c_str(),&st)==0);
     418             : // sharing devices, pipes, etc is legitimate
     419        3785 :   if(exists && !S_ISREG(st.st_mode)) {
     420             :     return;
     421             :   }
     422       14113 :   for(const auto & f : plumed->getFiles()) {
     423       10333 :     const auto other=dynamic_cast<const OFile*>(f);
     424             :     // input files are ignored: reading a file while writing it is allowed (e.g. METAD restart)
     425       10333 :     if(!other || other==this || !other->fp) {
     426        4461 :       continue;
     427             :     }
     428        5872 :     bool same=(other->path==path);
     429        5872 :     if(!same && exists) {
     430             :       // catch the same file accessed with a different path (e.g. ./FILE, absolute paths, or links)
     431             :       struct stat ost;
     432         329 :       if(fstat(fileno(other->fp),&ost)==0) {
     433         329 :         same=(ost.st_dev==st.st_dev && ost.st_ino==st.st_ino);
     434             :       }
     435             :     }
     436        5543 :     if(same) {
     437           5 :       std::string msg="file " + path + " is already open for writing";
     438           5 :       if(other->action) {
     439          10 :         msg+=" by action " + other->action->getLabel() + " (" + other->action->getName() + ")";
     440             :       }
     441           5 :       if(action) {
     442          15 :         msg+=" and cannot be opened again by action " + action->getLabel() + " (" + action->getName() + ")";
     443             :       }
     444             :       msg+=". Writing the same file twice would result in a corrupted file. Please use a different file name";
     445          10 :       plumed_merror(msg);
     446             :     }
     447             :   }
     448             : }
     449             : 
     450         183 : OFile& OFile::rewind() {
     451             : // we use here "hard" rewind, which means close/reopen
     452             : // the reason is that normal rewind does not work when in append mode
     453             : // moreover, we can take a backup of the file
     454         183 :   plumed_assert(fp);
     455         183 :   clearFields();
     456             : 
     457         183 :   if(!comm || comm->Get_rank()==0) {
     458         142 :     std::string fname=this->path;
     459         142 :     size_t found=fname.find_last_of("/\\");
     460         142 :     std::string directory=fname.substr(0,found+1);
     461         142 :     std::string file=fname.substr(found+1);
     462         284 :     std::string backup=directory+backstring +".last."+file;
     463         142 :     int check=rename(fname.c_str(),backup.c_str());
     464         142 :     plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+std::strerror(errno));
     465             :   }
     466             : 
     467         183 :   if(comm) {
     468         183 :     comm->Barrier();
     469             :   }
     470             : 
     471         183 :   if(gzfp) {
     472             : #ifdef __PLUMED_HAS_ZLIB
     473          15 :     gzclose((gzFile)gzfp);
     474             :     // no exception here
     475          15 :     gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"w9");
     476             : #endif
     477             :   } else {
     478         168 :     std::fclose(fp);
     479             :     // no exception here
     480         168 :     fp=std::fopen(const_cast<char*>(path.c_str()),"w");
     481             :   }
     482         183 :   return *this;
     483             : }
     484             : 
     485       10756 : FileBase& OFile::flush() {
     486       10756 :   if(heavyFlush) {
     487        3938 :     if(gzfp) {
     488             : #ifdef __PLUMED_HAS_ZLIB
     489           9 :       gzclose(gzFile(gzfp));
     490             :       // no exception here
     491           9 :       gzfp=(void*)gzopen(const_cast<char*>(path.c_str()),"a");
     492             : #endif
     493             :     } else {
     494        3929 :       std::fclose(fp);
     495             :       // no exception here
     496        3929 :       fp=std::fopen(const_cast<char*>(path.c_str()),"a");
     497             :     }
     498             :   } else {
     499        6818 :     FileBase::flush();
     500             :     // if(gzfp) gzflush(gzFile(gzfp),Z_FINISH);
     501             :     // for some reason flushing with Z_FINISH has problems on linux
     502             :     // I thus use this (incomplete) flush
     503             : #ifdef __PLUMED_HAS_ZLIB
     504        6818 :     if(gzfp) {
     505          31 :       gzflush(gzFile(gzfp),Z_FULL_FLUSH);
     506             :     }
     507             : #endif
     508             :   }
     509       10756 :   return *this;
     510             : }
     511             : 
     512        4428 : bool OFile::checkRestart()const {
     513        4428 :   if(enforceRestart_) {
     514             :     return true;
     515        4417 :   } else if(enforceBackup_) {
     516             :     return false;
     517        3273 :   } else if(action) {
     518        2808 :     return action->getRestart();
     519         465 :   } else if(plumed) {
     520           0 :     return plumed->getRestart();
     521             :   } else {
     522             :     return false;
     523             :   }
     524             : }
     525             : 
     526          11 : OFile& OFile::enforceRestart() {
     527          11 :   enforceRestart_=true;
     528          11 :   enforceBackup_=false;
     529          11 :   return *this;
     530             : }
     531             : 
     532        1144 : OFile& OFile::enforceBackup() {
     533        1144 :   enforceBackup_=true;
     534        1144 :   enforceRestart_=false;
     535        1144 :   return *this;
     536             : }
     537             : 
     538             : 
     539             : }

Generated by: LCOV version 1.16