// The contents of this file are subject to the BOINC Public License // Version 1.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://boinc.berkeley.edu/license_1.0.txt // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is the Berkeley Open Infrastructure for Network Computing. // // The Initial Developer of the Original Code is the SETI@home project. // Portions created by the SETI@home project are Copyright (C) 2002 // University of California at Berkeley. All Rights Reserved. // // Contributor(s): // // Untested and unsupported code for checkpointing multiple files. #include #include #include #include #include using namespace std; // abstract interface - derive classes from this base class class BoincCheckpointFile { public: // these functions should set error on f on failure // this is only called once each time an application is resumed virtual void input(istream& f) = 0; // this is called at every checkpoint virtual void output(ostream& f) = 0; }; // checkpoint class good for binary dump of struct as a state file class BoincRawDataCheckpointFile : public BoincCheckpointFile { void* data; size_t length; public: BoincRawDataCheckpointFile(void* data_, size_t length_) :data(data_), length(length_) { } virtual void input(istream& f) { f.read((char*) data, length); if (!f.eof()) f.clear(ios::badbit); } virtual void output(ostream& f) { f.write((char const*) data, length); } }; // Class that is good for writing or appending (text or binary) data. // Use standard C++ iostream operators to output, or printf class BoincStreamCheckpointFile : public BoincCheckpointFile, public stringstream { public: virtual void input(istream& f) { // read entire file into memory buffer (which is a stringbuf) f >> rdbuf(); } virtual void output(ostream& f) { // write entire memory buffer to file seekg(0); f << rdbuf(); } void printf(const char* format, ...) { va_list ap; char buf[20000]; va_start(ap, format); vsprintf(buf, format, ap); va_end(ap); *this << buf; } }; // AtomicFileSet defines a set of files which are written/read atomically. If // the system crashes within a write(), the next read() will read what was // written by the previous write(). Assumes that writing a single integer is // atomic - a much safer assumption than just writing an arbitrary number of // files of arbitrary length. class AtomicFileSet { struct NamedCheckpointFile { const char* filename; BoincCheckpointFile* file; bool keep; }; int current_checkpoint_number; typedef vector FilesList; FilesList files; // returns true on success bool read_checkpoint_number() { ifstream f("boinc_checkpoint.dat"); return (bool) (void*) (f >> current_checkpoint_number); } void write_checkpoint_number() const { ofstream f("boinc_checkpoint.dat"); f << current_checkpoint_number; } string format_checkpointed_filename(const char* filename, int checkpoint_number) { char buf[1024]; sprintf(buf, "%s.%d", filename, checkpoint_number); return buf; } void delete_files(int checkpoint_number) { for (FilesList::const_iterator i = files.begin(); i != files.end(); ++i) { string cp_filename = format_checkpointed_filename(i->filename, checkpoint_number); if (unlink(cp_filename.c_str())) { cerr << "Couldn't unlink \"" << cp_filename << "\": " << strerror(errno) << endl; } } } public: AtomicFileSet() : current_checkpoint_number(0) {} // add a file to the set of files to atomically write. if keep, then the // file exists as 'filename' after Finish(); else it is deleted after // Finish(). void add(const char* filename, BoincCheckpointFile* file, bool keep = true) { NamedCheckpointFile namedfile; namedfile.filename = filename; namedfile.file = file; namedfile.keep = keep; files.push_back(namedfile); } // call when resuming. returns true on success of all files, false on failure. bool read() { if (!read_checkpoint_number()) return false; for (FilesList::const_iterator i = files.begin(); i != files.end(); ++i) { string cp_filename = format_checkpointed_filename(i->filename, current_checkpoint_number); ifstream f(cp_filename.c_str(), ios::binary); if (!f) { cerr << "Error opening for input \"" << cp_filename << "\"\n"; return false; } i->file->input(f); if (!f) { cerr << "Error reading \"" << cp_filename << "\"\n"; return false; } } // success return true; } void write() { // strategy: // 1. write *.N // 2. write N to checkpoint file // 3. delete *.(N-1) for (FilesList::const_iterator i = files.begin(); i != files.end(); ++i) { string cp_filename = format_checkpointed_filename(i->filename, current_checkpoint_number); ofstream f(cp_filename.c_str(), ios::binary); if (!f) { cerr << "Couldn't open output \"" << cp_filename << "\"\n"; exit(101); } i->file->output(f); if (!f) { cerr << "Error writing \"" << cp_filename << "\"\n"; exit(102); } } write_checkpoint_number(); delete_files(current_checkpoint_number-1); ++current_checkpoint_number; } // NOTE: you must call write() yourself, if there is any data to be written. void finish() { // delete files that are no longer needed, and rename the ones we // want to keep. int checkpoint_number = current_checkpoint_number-1; for (FilesList::const_iterator i = files.begin(); i != files.end(); ++i) { string cp_filename = format_checkpointed_filename(i->filename, checkpoint_number); if (i->keep) { if (unlink(cp_filename.c_str())) { cerr << "Warning: Couldn't unlink \"" << cp_filename << "\": " << strerror(errno) << endl; } } else { if (rename(cp_filename.c_str(), i->filename)) { cerr << "Fatal error: Couldn't rename \"" << cp_filename << "\" to \"" << i->filename << "\": " << strerror(errno) << endl; exit(100); } } } } }; /* usage in astropulse: AtomicFileSet files; ostream* output; void init() { files.add("ap_state.dat", new BoincRawDataCheckpointFile(client.state, sizeof(client.state))); files.add("pulse.out", (output=new BoincStreamCheckpointFile)); if (files.read()) { // resuming } else { *output << " ..."; } } void output_pulse() { *output << " ... "; } void checkpoint() { files.write(); } */