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