1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2012-2021 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 #ifdef __PLUMED_HAS_ZLIB
41 #include <zlib.h>
42 #endif
43 
44 namespace PLMD {
45 
llwrite(const char * ptr,size_t s)46 size_t OFile::llwrite(const char*ptr,size_t s) {
47   size_t r;
48   if(linked) return linked->llwrite(ptr,s);
49   if(! (comm && comm->Get_rank()>0)) {
50     if(!fp) plumed_merror("writing on uninitialized File");
51     if(gzfp) {
52 #ifdef __PLUMED_HAS_ZLIB
53       r=gzwrite(gzFile(gzfp),ptr,s);
54 #else
55       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
56 #endif
57     } else {
58       r=fwrite(ptr,1,s,fp);
59     }
60   }
61 //  This barrier is apparently useless since it comes
62 //  just before a Bcast.
63 //
64 //  Anyway, it looks like it is solving an issue that appeared on
65 //  TRAVIS (at least on my laptop) so I add it here.
66 //  GB
67   if(comm) comm->Barrier();
68 
69 
70   if(comm) comm->Bcast(r,0);
71   return r;
72 }
73 
OFile()74 OFile::OFile():
75   linked(NULL),
76   fieldChanged(false),
77   backstring("bck"),
78   enforceRestart_(false),
79   enforceBackup_(false)
80 {
81   fmtField();
82   buflen=1;
83   actual_buffer_length=0;
84   buffer.reset(new char [buflen]);
85 // these are set to zero to avoid valgrind errors
86   for(int i=0; i<buflen; ++i) buffer[i]=0;
87   buffer_string.reset(new char [1000]);
88 // these are set to zero to avoid valgrind errors
89   for(unsigned i=0; i<1000; ++i) buffer_string[i]=0;
90 }
91 
link(OFile & l)92 OFile& OFile::link(OFile&l) {
93   fp=NULL;
94   gzfp=NULL;
95   linked=&l;
96   return *this;
97 }
98 
setLinePrefix(const std::string & l)99 OFile& OFile::setLinePrefix(const std::string&l) {
100   linePrefix=l;
101   return *this;
102 }
103 
printf(const char * fmt,...)104 int OFile::printf(const char*fmt,...) {
105   va_list arg;
106   va_start(arg, fmt);
107   int r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,arg);
108   va_end(arg);
109   if(r>=buflen-actual_buffer_length) {
110     int newlen=buflen;
111     while(newlen<=r+actual_buffer_length) newlen*=2;
112     std::unique_ptr<char[]> newbuf{new char [newlen]};
113     memmove(newbuf.get(),buffer.get(),buflen);
114     for(int k=buflen; k<newlen; k++) newbuf[k]=0;
115     buffer=std::move(newbuf);
116     buflen=newlen;
117     va_list arg;
118     va_start(arg, fmt);
119     r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,arg);
120     va_end(arg);
121   }
122   plumed_massert(r>-1 && r<buflen-actual_buffer_length,"error using fmt string " + std::string(fmt));
123 
124 // Line is buffered until newline, then written with a PLUMED: prefix
125   char*p1=buffer.get();
126   char*p2;
127 // newline is only searched in the just added portion:
128   char*psearch=p1+actual_buffer_length;
129   actual_buffer_length+=r;
130   while((p2=strchr(psearch,'\n'))) {
131     if(linePrefix.length()>0) llwrite(linePrefix.c_str(),linePrefix.length());
132     llwrite(p1,p2-p1+1);
133     actual_buffer_length-=(p2-p1)+1;
134     p1=p2+1;
135     psearch=p1;
136   };
137   if(buffer.get()!=p1) memmove(buffer.get(),p1,actual_buffer_length);
138   return r;
139 }
140 
addConstantField(const std::string & name)141 OFile& OFile::addConstantField(const std::string&name) {
142   Field f;
143   f.name=name;
144   const_fields.push_back(f);
145   return *this;
146 }
147 
148 
clearFields()149 OFile& OFile::clearFields() {
150   fields.clear();
151   const_fields.clear();
152   previous_fields.clear();
153   return *this;
154 }
155 
fmtField(const std::string & fmt)156 OFile& OFile::fmtField(const std::string&fmt) {
157   this->fieldFmt=fmt;
158   return *this;
159 }
160 
fmtField()161 OFile& OFile::fmtField() {
162   this->fieldFmt="%23.16lg";
163   return *this;
164 }
165 
printField(const std::string & name,double v)166 OFile& OFile::printField(const std::string&name,double v) {
167 // When one tries to print -nan we print nan instead.
168 // The distinction between +nan and -nan is not well defined
169 // Always printing nan simplifies some regtest (special functions computed our of range).
170   if(std::isnan(v)) v=std::numeric_limits<double>::quiet_NaN();
171   sprintf(buffer_string.get(),fieldFmt.c_str(),v);
172   printField(name,buffer_string.get());
173   return *this;
174 }
175 
printField(const std::string & name,int v)176 OFile& OFile::printField(const std::string&name,int v) {
177   sprintf(buffer_string.get()," %d",v);
178   printField(name,buffer_string.get());
179   return *this;
180 }
181 
printField(const std::string & name,long int v)182 OFile& OFile::printField(const std::string&name,long int v) {
183   sprintf(buffer_string.get()," %ld",v);
184   printField(name,buffer_string.get());
185   return *this;
186 }
187 
printField(const std::string & name,unsigned v)188 OFile& OFile::printField(const std::string&name,unsigned v) {
189   sprintf(buffer_string.get()," %u",v);
190   printField(name,buffer_string.get());
191   return *this;
192 }
193 
printField(const std::string & name,long unsigned v)194 OFile& OFile::printField(const std::string&name,long unsigned v) {
195   sprintf(buffer_string.get()," %lu",v);
196   printField(name,buffer_string.get());
197   return *this;
198 }
199 
printField(const std::string & name,const std::string & v)200 OFile& OFile::printField(const std::string&name,const std::string & v) {
201   unsigned i;
202   for(i=0; i<const_fields.size(); i++) if(const_fields[i].name==name) break;
203   if(i>=const_fields.size()) {
204     Field field;
205     field.name=name;
206     field.value=v;
207     fields.push_back(field);
208   } else {
209     if(const_fields[i].value!=v) fieldChanged=true;
210     const_fields[i].value=v;
211   }
212   return *this;
213 }
214 
setupPrintValue(Value * val)215 OFile& OFile::setupPrintValue( Value *val ) {
216   if( val->isPeriodic() ) {
217     addConstantField("min_" + val->getName() );
218     addConstantField("max_" + val->getName() );
219   }
220   return *this;
221 }
222 
printField(Value * val,const double & v)223 OFile& OFile::printField( Value* val, const double& v ) {
224   printField( val->getName(), v );
225   if( val->isPeriodic() ) {
226     std::string min, max; val->getDomain( min, max );
227     printField( "min_" + val->getName(), min );
228     printField("max_" + val->getName(), max );
229   }
230   return *this;
231 }
232 
printField()233 OFile& OFile::printField() {
234   bool reprint=false;
235   if(fieldChanged || fields.size()!=previous_fields.size()) {
236     reprint=true;
237   } else for(unsigned i=0; i<fields.size(); i++) {
238       if( previous_fields[i].name!=fields[i].name ||
239           (fields[i].constant && fields[i].value!=previous_fields[i].value) ) {
240         reprint=true;
241         break;
242       }
243     }
244   if(reprint) {
245     printf("#! FIELDS");
246     for(unsigned i=0; i<fields.size(); i++) printf(" %s",fields[i].name.c_str());
247     printf("\n");
248     for(unsigned i=0; i<const_fields.size(); i++) {
249       printf("#! SET %s %s",const_fields[i].name.c_str(),const_fields[i].value.c_str());
250       printf("\n");
251     }
252   }
253   for(unsigned i=0; i<fields.size(); i++) printf("%s",fields[i].value.c_str());
254   printf("\n");
255   previous_fields=fields;
256   fields.clear();
257   fieldChanged=false;
258   return *this;
259 }
260 
setBackupString(const std::string & str)261 void OFile::setBackupString( const std::string& str ) {
262   backstring=str;
263 }
264 
backupAllFiles(const std::string & str)265 void OFile::backupAllFiles( const std::string& str ) {
266   if(str=="/dev/null") return;
267   plumed_assert( backstring!="bck" && !checkRestart());
268   size_t found=str.find_last_of("/\\");
269   std::string filename = appendSuffix(str,getSuffix());
270   std::string directory=filename.substr(0,found+1);
271   std::string file=filename.substr(found+1);
272   if( FileExist(filename) ) backupFile("bck", filename);
273   for(int i=0;; i++) {
274     std::string num; Tools::convert(i,num);
275     std::string filestr = directory + backstring + "." + num + "." + file;
276     if( !FileExist(filestr) ) break;
277     backupFile( "bck", filestr);
278   }
279 }
280 
backupFile(const std::string & bstring,const std::string & fname)281 void OFile::backupFile( const std::string& bstring, const std::string& fname ) {
282   if(fname=="/dev/null") return;
283   int maxbackup=100;
284   if(std::getenv("PLUMED_MAXBACKUP")) Tools::convert(std::getenv("PLUMED_MAXBACKUP"),maxbackup);
285   if(maxbackup>0 && (!comm || comm->Get_rank()==0)) {
286     FILE* ff=std::fopen(const_cast<char*>(fname.c_str()),"r");
287     if(ff) {
288       std::fclose(ff);
289       std::string backup;
290       size_t found=fname.find_last_of("/\\");
291       std::string directory=fname.substr(0,found+1);
292       std::string file=fname.substr(found+1);
293       for(int i=0;; i++) {
294         std::string num;
295         Tools::convert(i,num);
296         if(i>maxbackup) plumed_merror("cannot backup file "+file+" maximum number of backup is "+num+"\n");
297         backup=directory+bstring +"."+num+"."+file;
298         FILE* fff=std::fopen(backup.c_str(),"r");
299         if(!fff) break;
300         else std::fclose(fff);
301       }
302       int check=rename(fname.c_str(),backup.c_str());
303       plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+strerror(errno));
304     }
305   }
306 }
307 
open(const std::string & path)308 OFile& OFile::open(const std::string&path) {
309   plumed_assert(!cloned);
310   eof=false;
311   err=false;
312   fp=NULL;
313   gzfp=NULL;
314   this->path=path;
315   this->path=appendSuffix(path,getSuffix());
316   if(checkRestart()) {
317     fp=std::fopen(const_cast<char*>(this->path.c_str()),"a");
318     mode="a";
319     if(Tools::extension(this->path)=="gz") {
320 #ifdef __PLUMED_HAS_ZLIB
321       gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"a9");
322 #else
323       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
324 #endif
325     }
326   } else {
327     backupFile( backstring, this->path );
328     if(comm)comm->Barrier();
329     fp=std::fopen(const_cast<char*>(this->path.c_str()),"w");
330     mode="w";
331     if(Tools::extension(this->path)=="gz") {
332 #ifdef __PLUMED_HAS_ZLIB
333       gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"w9");
334 #else
335       plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
336 #endif
337     }
338   }
339   if(plumed) plumed->insertFile(*this);
340   return *this;
341 }
342 
rewind()343 OFile& OFile::rewind() {
344 // we use here "hard" rewind, which means close/reopen
345 // the reason is that normal rewind does not work when in append mode
346 // moreover, we can take a backup of the file
347   plumed_assert(fp);
348   clearFields();
349   if(gzfp) {
350 #ifdef __PLUMED_HAS_ZLIB
351     gzclose((gzFile)gzfp);
352 #endif
353   } else fclose(fp);
354 
355   if(!comm || comm->Get_rank()==0) {
356     std::string fname=this->path;
357     size_t found=fname.find_last_of("/\\");
358     std::string directory=fname.substr(0,found+1);
359     std::string file=fname.substr(found+1);
360     std::string backup=directory+backstring +".last."+file;
361     int check=rename(fname.c_str(),backup.c_str());
362     plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+strerror(errno));
363   }
364 
365   if(comm) comm->Barrier();
366 
367   if(gzfp) {
368 #ifdef __PLUMED_HAS_ZLIB
369     gzfp=(void*)gzopen(const_cast<char*>(this->path.c_str()),"w9");
370 #endif
371   } else fp=std::fopen(const_cast<char*>(path.c_str()),"w");
372   return *this;
373 }
374 
flush()375 FileBase& OFile::flush() {
376   if(heavyFlush) {
377     if(gzfp) {
378 #ifdef __PLUMED_HAS_ZLIB
379       gzclose(gzFile(gzfp));
380       gzfp=(void*)gzopen(const_cast<char*>(path.c_str()),"a");
381 #endif
382     } else {
383       fclose(fp);
384       fp=std::fopen(const_cast<char*>(path.c_str()),"a");
385     }
386   } else {
387     FileBase::flush();
388     // if(gzfp) gzflush(gzFile(gzfp),Z_FINISH);
389     // for some reason flushing with Z_FINISH has problems on linux
390     // I thus use this (incomplete) flush
391 #ifdef __PLUMED_HAS_ZLIB
392     if(gzfp) gzflush(gzFile(gzfp),Z_FULL_FLUSH);
393 #endif
394   }
395   return *this;
396 }
397 
checkRestart() const398 bool OFile::checkRestart()const {
399   if(enforceRestart_) return true;
400   else if(enforceBackup_) return false;
401   else if(action) return action->getRestart();
402   else if(plumed) return plumed->getRestart();
403   else return false;
404 }
405 
enforceRestart()406 OFile& OFile::enforceRestart() {
407   enforceRestart_=true;
408   enforceBackup_=false;
409   return *this;
410 }
411 
enforceBackup()412 OFile& OFile::enforceBackup() {
413   enforceBackup_=true;
414   enforceRestart_=false;
415   return *this;
416 }
417 
418 
419 }
420