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 <string>
33 : #include <cstdlib>
34 : #include <cerrno>
35 : #include <utility>
36 :
37 : #include <sys/types.h>
38 : #include <sys/stat.h>
39 :
40 : #ifdef __PLUMED_HAS_ZLIB
41 : #include <zlib.h>
42 : #endif
43 :
44 : namespace PLMD {
45 :
46 6991287 : size_t OFile::llwrite(const char*ptr,size_t s) {
47 6991287 : if(linked) {
48 52 : return linked->llwrite(ptr,s);
49 : }
50 : // Only the first process writes. Until v2.10 this was followed by a Barrier and by a
51 : // Bcast of the number of written bytes, i.e. two collective operations per line,
52 : // which slowed down runs with many processes and hung if the processes did not
53 : // write the same number of lines. The return value is not used.
54 6991235 : if(comm && comm->Get_rank()>0) {
55 : return s;
56 : }
57 5986188 : if(!fp) {
58 0 : plumed_merror("writing on uninitialized File");
59 : }
60 : size_t r=0;
61 5986188 : if(gzfp) {
62 : #ifdef __PLUMED_HAS_ZLIB
63 1265 : r=gzwrite(gzFile(gzfp),ptr,s);
64 : #else
65 : plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
66 : #endif
67 : } else {
68 5984923 : r=std::fwrite(ptr,1,s,fp);
69 : }
70 : return r;
71 : }
72 :
73 821661 : OFile::OFile():
74 821661 : linked(NULL),
75 821661 : fieldChanged(false),
76 821661 : backstring("bck"),
77 821661 : enforceRestart_(false),
78 821661 : enforceBackup_(false) {
79 821661 : fmtField();
80 821661 : buflen=1;
81 821661 : actual_buffer_length=0;
82 821661 : buffer.resize(buflen);
83 : // these are set to zero to avoid valgrind errors
84 1643322 : for(int i=0; i<buflen; ++i) {
85 821661 : buffer[i]=0;
86 : }
87 : // these are set to zero to avoid valgrind errors
88 821661 : buffer_string.resize(1000,0);
89 821661 : }
90 :
91 14 : OFile& OFile::link(OFile&l) {
92 14 : fp=NULL;
93 14 : gzfp=NULL;
94 14 : linked=&l;
95 14 : return *this;
96 : }
97 :
98 809627 : OFile& OFile::setLinePrefix(const std::string&l) {
99 809627 : linePrefix=l;
100 809627 : return *this;
101 : }
102 :
103 28361773 : int OFile::printf(const char*fmt,...) {
104 : va_list arg;
105 28361773 : va_start(arg, fmt);
106 28361773 : int r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,arg);
107 28361773 : va_end(arg);
108 28361773 : if(r>=buflen-actual_buffer_length) {
109 : int newlen=buflen;
110 81470 : while(newlen<=r+actual_buffer_length) {
111 53801 : newlen*=2;
112 : }
113 27669 : std::vector<char> newbuf(newlen);
114 27669 : std::memmove(newbuf.data(),buffer.data(),buflen);
115 16808090 : for(int k=buflen; k<newlen; k++) {
116 16780421 : newbuf[k]=0;
117 : }
118 : std::swap(buffer,newbuf);
119 27669 : buflen=newlen;
120 : va_list args;
121 27669 : va_start(args, fmt);
122 27669 : r=std::vsnprintf(&buffer[actual_buffer_length],buflen-actual_buffer_length,fmt,args);
123 27669 : va_end(args);
124 27669 : }
125 28361773 : plumed_massert(r>-1 && r<buflen-actual_buffer_length,"error using fmt string " + std::string(fmt));
126 :
127 : // Line is buffered until newline, then written with a PLUMED: prefix
128 : char*p1=buffer.data();
129 : char*p2;
130 : // newline is only searched in the just added portion:
131 28361773 : char*psearch=p1+actual_buffer_length;
132 28361773 : actual_buffer_length+=r;
133 34486495 : while((p2=std::strchr(psearch,'\n'))) {
134 6124722 : if(linePrefix.length()>0) {
135 866513 : llwrite(linePrefix.c_str(),linePrefix.length());
136 : }
137 6124722 : llwrite(p1,p2-p1+1);
138 6124722 : actual_buffer_length-=(p2-p1)+1;
139 6124722 : p1=p2+1;
140 : psearch=p1;
141 : };
142 28361773 : if(buffer.data()!=p1) {
143 6099704 : std::memmove(buffer.data(),p1,actual_buffer_length);
144 : }
145 28361773 : return r;
146 : }
147 :
148 30858 : OFile& OFile::addConstantField(const std::string&name) {
149 : Field f;
150 : f.name=name;
151 30858 : const_fields.push_back(f);
152 30858 : return *this;
153 : }
154 :
155 :
156 3681 : OFile& OFile::clearFields() {
157 3681 : fields.clear();
158 3681 : const_fields.clear();
159 3681 : previous_fields.clear();
160 3681 : return *this;
161 : }
162 :
163 15942532 : OFile& OFile::fmtField(const std::string&fmt) {
164 15942532 : this->fieldFmt=fmt;
165 15942532 : return *this;
166 : }
167 :
168 827532 : OFile& OFile::fmtField() {
169 827532 : this->fieldFmt="%23.16lg";
170 827532 : return *this;
171 : }
172 :
173 17948842 : OFile& OFile::printField(const std::string&name,double v) {
174 : // When one tries to print -nan we print nan instead.
175 : // The distinction between +nan and -nan is not well defined
176 : // Always printing nan simplifies some regtest (special functions computed our of range).
177 17948842 : if(std::isnan(v)) {
178 : v=std::numeric_limits<double>::quiet_NaN();
179 : }
180 : std::snprintf(buffer_string.data(),buffer_string.size(),fieldFmt.c_str(),v);
181 17948842 : printField(name,buffer_string.data());
182 17948842 : return *this;
183 : }
184 :
185 6522567 : OFile& OFile::printField(const std::string&name,int v) {
186 : std::snprintf(buffer_string.data(),buffer_string.size()," %d",v);
187 6522567 : printField(name,buffer_string.data());
188 6522567 : return *this;
189 : }
190 :
191 1 : OFile& OFile::printField(const std::string&name,long int v) {
192 : std::snprintf(buffer_string.data(),buffer_string.size()," %ld",v);
193 1 : printField(name,buffer_string.data());
194 1 : return *this;
195 : }
196 :
197 0 : OFile& OFile::printField(const std::string&name,long long int v) {
198 : std::snprintf(buffer_string.data(),buffer_string.size()," %lld",v);
199 0 : printField(name,buffer_string.data());
200 0 : return *this;
201 : }
202 :
203 267 : OFile& OFile::printField(const std::string&name,unsigned v) {
204 : std::snprintf(buffer_string.data(),buffer_string.size()," %u",v);
205 267 : printField(name,buffer_string.data());
206 267 : return *this;
207 : }
208 :
209 1 : OFile& OFile::printField(const std::string&name,long unsigned v) {
210 : std::snprintf(buffer_string.data(),buffer_string.size()," %lu",v);
211 1 : printField(name,buffer_string.data());
212 1 : return *this;
213 : }
214 :
215 137 : OFile& OFile::printField(const std::string&name,long long unsigned v) {
216 : std::snprintf(buffer_string.data(),buffer_string.size()," %llu",v);
217 137 : printField(name,buffer_string.data());
218 137 : return *this;
219 : }
220 :
221 41621072 : OFile& OFile::printField(const std::string&name,const std::string & v) {
222 : unsigned i;
223 211977882 : for(i=0; i<const_fields.size(); i++)
224 192776950 : if(const_fields[i].name==name) {
225 : break;
226 : }
227 41621072 : if(i>=const_fields.size()) {
228 : Field field;
229 : field.name=name;
230 : field.value=v;
231 19200932 : fields.push_back(field);
232 : } else {
233 22420140 : if(const_fields[i].value!=v) {
234 30774 : fieldChanged=true;
235 : }
236 : const_fields[i].value=v;
237 : }
238 41621072 : return *this;
239 : }
240 :
241 9038 : OFile& OFile::setupPrintValue( Value *val ) {
242 9038 : if( val->isPeriodic() ) {
243 869 : addConstantField("min_" + val->getName() );
244 1738 : addConstantField("max_" + val->getName() );
245 : }
246 9038 : return *this;
247 : }
248 :
249 55084 : OFile& OFile::printField( Value* val, const double& v ) {
250 55084 : printField( val->getName(), v );
251 55084 : if( val->isPeriodic() ) {
252 : std::string min, max;
253 14804 : val->getDomain( min, max );
254 14804 : printField( "min_" + val->getName(), min );
255 29608 : printField("max_" + val->getName(), max );
256 : }
257 55084 : return *this;
258 : }
259 :
260 4190129 : OFile& OFile::printField() {
261 : bool reprint=false;
262 4190129 : if(fieldChanged || fields.size()!=previous_fields.size()) {
263 : reprint=true;
264 : } else
265 22707555 : for(unsigned i=0; i<fields.size(); i++) {
266 18524927 : if( previous_fields[i].name!=fields[i].name ||
267 18524925 : (fields[i].constant && fields[i].value!=previous_fields[i].value) ) {
268 : reprint=true;
269 : break;
270 : }
271 : }
272 4190129 : if(reprint) {
273 7501 : printf("#! FIELDS");
274 683510 : for(unsigned i=0; i<fields.size(); i++) {
275 676009 : printf(" %s",fields[i].name.c_str());
276 : }
277 7501 : printf("\n");
278 38069 : for(unsigned i=0; i<const_fields.size(); i++) {
279 30568 : printf("#! SET %s %s",const_fields[i].name.c_str(),const_fields[i].value.c_str());
280 30568 : printf("\n");
281 : }
282 : }
283 23391061 : for(unsigned i=0; i<fields.size(); i++) {
284 19200932 : printf("%s",fields[i].value.c_str());
285 : }
286 4190129 : printf("\n");
287 4190129 : previous_fields=fields;
288 4190129 : fields.clear();
289 4190129 : fieldChanged=false;
290 4190129 : return *this;
291 : }
292 :
293 234 : void OFile::setBackupString( const std::string& str ) {
294 234 : backstring=str;
295 234 : }
296 :
297 0 : void OFile::backupAllFiles( const std::string& str ) {
298 0 : if(str=="/dev/null") {
299 0 : return;
300 : }
301 0 : plumed_assert( backstring!="bck" && !checkRestart());
302 0 : size_t found=str.find_last_of("/\\");
303 0 : std::string filename = appendSuffix(str,getSuffix());
304 0 : std::string directory=filename.substr(0,found+1);
305 0 : std::string file=filename.substr(found+1);
306 0 : if( FileExist(filename) ) {
307 0 : backupFile("bck", filename);
308 : }
309 0 : for(int i=0;; i++) {
310 : std::string num;
311 0 : Tools::convert(i,num);
312 0 : std::string filestr = directory + backstring + "." + num + "." + file;
313 0 : if( !FileExist(filestr) ) {
314 : break;
315 : }
316 0 : backupFile( "bck", filestr);
317 0 : }
318 : }
319 :
320 4673 : void OFile::backupFile( const std::string& bstring, const std::string& fname ) {
321 4673 : if(fname=="/dev/null") {
322 271 : return;
323 : }
324 4402 : int maxbackup=100;
325 4402 : if(std::getenv("PLUMED_MAXBACKUP")) {
326 78 : Tools::convert(std::getenv("PLUMED_MAXBACKUP"),maxbackup);
327 : }
328 4402 : if(maxbackup>0 && (!comm || comm->Get_rank()==0)) {
329 3712 : FILE* ff=std::fopen(const_cast<char*>(fname.c_str()),"r");
330 3712 : if(ff) {
331 : // no exception here
332 150 : std::fclose(ff);
333 : std::string backup;
334 150 : size_t found=fname.find_last_of("/\\");
335 150 : std::string directory=fname.substr(0,found+1);
336 150 : std::string file=fname.substr(found+1);
337 150 : for(int i=0;; i++) {
338 : std::string num;
339 224 : Tools::convert(i,num);
340 224 : if(i>maxbackup) {
341 0 : plumed_merror("cannot backup file "+file+" maximum number of backup is "+num+"\n");
342 : }
343 896 : backup=directory+bstring +"."+num+"."+file;
344 224 : FILE* fff=std::fopen(backup.c_str(),"r");
345 : // no exception here
346 224 : if(!fff) {
347 : break;
348 : } else {
349 74 : std::fclose(fff);
350 : }
351 74 : }
352 150 : int check=rename(fname.c_str(),backup.c_str());
353 150 : plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+std::strerror(errno));
354 : }
355 : }
356 : }
357 :
358 5292 : OFile& OFile::open(const std::string&setpath) {
359 5292 : plumed_assert(!cloned);
360 5292 : eof=false;
361 5292 : err=false;
362 5292 : fp=NULL;
363 5292 : gzfp=NULL;
364 5292 : path=setpath;
365 5292 : path=appendSuffix(setpath,getSuffix());
366 : // this must be done before backing up the file, otherwise
367 : // the backup would rename the file that is being written by another OFile
368 5292 : checkNotOpenElsewhere();
369 : // Only the writer opens the file (the other processes open /dev/null):
370 : // with many processes on a parallel file system, opening (and truncating)
371 : // the same file from all of them is slow.
372 5281 : if(checkRestart()) {
373 619 : openOnWriter("a","a9");
374 619 : mode="a";
375 : } else {
376 4662 : backupFile( backstring, path );
377 4662 : if(comm) {
378 4599 : comm->Barrier();
379 : }
380 4662 : openOnWriter("w","w9");
381 4662 : mode="w";
382 : }
383 5281 : syncOpen();
384 5281 : if(plumed) {
385 4858 : plumed->insertFile(*this);
386 : }
387 5281 : return *this;
388 : }
389 :
390 5292 : void OFile::checkNotOpenElsewhere()const {
391 5292 : if(!plumed) {
392 728 : return;
393 : }
394 4869 : if(path=="/dev/null") {
395 : return;
396 : }
397 : // The files are compared on the writer only: the other processes have the null device
398 : // as file pointer (see openOnWriter), and the files could be different if paths
399 : // are local to nodes. The verdict is then broadcast, so that all processes
400 : // stop with the same error (if only the writer stopped, the others would hang).
401 3921 : const auto findDuplicate=[&]() {
402 : std::string msg;
403 : struct stat st;
404 3921 : const bool exists=(stat(path.c_str(),&st)==0);
405 : // sharing devices, pipes, etc is legitimate
406 : // (S_ISREG is not defined by MSVC, so the file type is tested explicitly)
407 3921 : if(exists && (st.st_mode & S_IFMT)!=S_IFREG) {
408 : return std::string();
409 : }
410 14749 : for(const auto & f : plumed->getFiles()) {
411 10836 : const auto other=dynamic_cast<const OFile*>(f);
412 : // input files are ignored: reading a file while writing it is allowed (e.g. METAD restart)
413 10836 : if(!other || other==this || !other->fp) {
414 4891 : continue;
415 : }
416 5945 : bool same=(other->path==path);
417 : #ifndef _WIN32
418 : // On Windows st_ino is always zero, so two different files on the same drive
419 : // would look the same: there only the paths are compared
420 5945 : if(!same && exists) {
421 : // catch the same file accessed with a different path (e.g. ./FILE, absolute paths, or links)
422 : struct stat ost;
423 420 : if(fstat(fileno(other->fp),&ost)==0) {
424 420 : same=(ost.st_dev==st.st_dev && ost.st_ino==st.st_ino);
425 : }
426 : }
427 : #endif
428 5525 : if(same) {
429 16 : msg="file " + path + " is already open for writing";
430 8 : if(other->action) {
431 24 : msg+=" by action " + other->action->getLabel() + " (" + other->action->getName() + ")";
432 : }
433 8 : if(action) {
434 24 : msg+=" and cannot be opened again by action " + action->getLabel() + " (" + action->getName() + ")";
435 : }
436 : msg+=". Writing the same file twice would result in a corrupted file. Please use a different file name";
437 : break;
438 : }
439 : }
440 3921 : return msg;
441 4564 : };
442 : std::string msg;
443 4564 : if(isWriter()) {
444 7842 : msg=findDuplicate();
445 : }
446 4564 : if(comm) {
447 4564 : unsigned len=msg.length();
448 4564 : comm->Bcast(len,0);
449 4575 : std::vector<char> chars(msg.begin(),msg.end());
450 4564 : chars.resize(len);
451 4564 : if(len>0) {
452 11 : comm->Bcast(chars,0);
453 : }
454 4564 : msg.assign(chars.begin(),chars.end());
455 4564 : }
456 4564 : if(!msg.empty()) {
457 22 : plumed_merror(msg);
458 : }
459 : }
460 :
461 14844 : bool OFile::isWriter()const {
462 14844 : return !comm || comm->Get_rank()==0;
463 : }
464 :
465 10280 : void OFile::openOnWriter(const char* fmode,const char* gzmode,bool reopenFp) {
466 : #ifdef _WIN32
467 : constexpr const char* nullDevice="NUL";
468 : #else
469 : constexpr const char* nullDevice="/dev/null";
470 : #endif
471 10280 : const char* p=isWriter() ? path.c_str() : nullDevice;
472 10280 : if(reopenFp) {
473 10256 : fp=std::fopen(p,fmode);
474 : }
475 10280 : if(Tools::extension(path)=="gz") {
476 : #ifdef __PLUMED_HAS_ZLIB
477 50 : gzfp=(void*)gzopen(p,gzmode);
478 : #else
479 : (void) gzmode;
480 : plumed_merror("file " + getPath() + ": trying to use a gz file without zlib being linked");
481 : #endif
482 : }
483 10280 : }
484 :
485 5854 : void OFile::syncOpen() {
486 5854 : if(!comm) {
487 64 : return;
488 : }
489 5790 : int ok=(fp!=NULL);
490 5790 : comm->Bcast(ok,0);
491 5790 : if(!ok && fp) {
492 : // the writer could not open the file: behave as if no process could
493 0 : std::fclose(fp);
494 0 : fp=NULL;
495 : #ifdef __PLUMED_HAS_ZLIB
496 0 : if(gzfp) {
497 0 : gzclose(gzFile(gzfp));
498 : }
499 : #endif
500 0 : gzfp=NULL;
501 : }
502 : }
503 :
504 573 : OFile& OFile::rewind() {
505 : // we use here "hard" rewind, which means close/reopen
506 : // the reason is that normal rewind does not work when in append mode
507 : // moreover, we can take a backup of the file
508 573 : plumed_assert(fp);
509 573 : clearFields();
510 :
511 573 : if(!comm || comm->Get_rank()==0) {
512 406 : std::string fname=this->path;
513 406 : size_t found=fname.find_last_of("/\\");
514 406 : std::string directory=fname.substr(0,found+1);
515 406 : std::string file=fname.substr(found+1);
516 1218 : std::string backup=directory+backstring +".last."+file;
517 406 : int check=rename(fname.c_str(),backup.c_str());
518 406 : plumed_massert(check==0,"renaming "+fname+" into "+backup+" failed for reason: "+std::strerror(errno));
519 : }
520 :
521 573 : if(comm) {
522 573 : comm->Barrier();
523 : }
524 :
525 573 : if(gzfp) {
526 : #ifdef __PLUMED_HAS_ZLIB
527 15 : gzclose((gzFile)gzfp);
528 : // no exception here
529 15 : openOnWriter("w","w9",false);
530 : #endif
531 : } else {
532 558 : std::fclose(fp);
533 : // no exception here
534 558 : openOnWriter("w","w9");
535 : }
536 : // see OFile::open
537 573 : syncOpen();
538 573 : return *this;
539 : }
540 :
541 14564 : FileBase& OFile::flush() {
542 14564 : if(heavyFlush) {
543 4426 : if(gzfp) {
544 : #ifdef __PLUMED_HAS_ZLIB
545 9 : gzclose(gzFile(gzfp));
546 : // no exception here
547 9 : openOnWriter("a","a",false);
548 : #endif
549 : } else {
550 4417 : std::fclose(fp);
551 : // no exception here
552 4417 : openOnWriter("a","a");
553 : }
554 : } else {
555 10138 : FileBase::flush();
556 : // if(gzfp) gzflush(gzFile(gzfp),Z_FINISH);
557 : // for some reason flushing with Z_FINISH has problems on linux
558 : // I thus use this (incomplete) flush
559 : #ifdef __PLUMED_HAS_ZLIB
560 10138 : if(gzfp) {
561 31 : gzflush(gzFile(gzfp),Z_FULL_FLUSH);
562 : }
563 : #endif
564 : }
565 14564 : return *this;
566 : }
567 :
568 5281 : bool OFile::checkRestart()const {
569 5281 : if(enforceRestart_) {
570 : return true;
571 5270 : } else if(enforceBackup_) {
572 : return false;
573 4125 : } else if(action) {
574 3703 : return action->getRestart();
575 422 : } else if(plumed) {
576 0 : return plumed->getRestart();
577 : } else {
578 : return false;
579 : }
580 : }
581 :
582 11 : OFile& OFile::enforceRestart() {
583 11 : enforceRestart_=true;
584 11 : enforceBackup_=false;
585 11 : return *this;
586 : }
587 :
588 1145 : OFile& OFile::enforceBackup() {
589 1145 : enforceBackup_=true;
590 1145 : enforceRestart_=false;
591 1145 : return *this;
592 : }
593 :
594 :
595 : }
|