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 : }
|