#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <stdint.h>
#include "rebound.h"
#include "rebound_internal.h"
#include "particle.h"
#include "fmemopen.h"
#include "binarydata.h"
#include "output.h"
#include "tools.h"
#include "output.h"
#include "simulationarchive.h"
#include "simulation.h"
#ifdef MPI
#include "communication_mpi.h"
#endif
void reb_simulation_init_from_simulationarchive_with_messages(struct reb_simulation* r, struct reb_simulationarchive* sa, int64_t snapshot, enum REB_BINARYDATA_ERROR_CODE* warnings){
FILE* inf = sa->inf;
if (inf == NULL){
*warnings |= REB_BINARYDATA_ERROR_FILENOTOPEN;
return;
}
if (snapshot<0) snapshot += sa->nblobs;
if (snapshot>=sa->nblobs || snapshot<0){
*warnings |= REB_BINARYDATA_ERROR_OUTOFRANGE;
return;
}
#ifdef MPI
reb_communication_mpi_init(r, 0, NULL);
#endif r->simulationarchive_filename = NULL;
fseek(inf, 0, SEEK_SET);
reb_binarydata_input_fields(r, inf, warnings);
if (snapshot==0) return;
if(fseek(inf, sa->offset[snapshot], SEEK_SET)){
*warnings |= REB_BINARYDATA_ERROR_SEEK;
return;
}
if (r->simulationarchive_version<5){
*warnings |= REB_BINARYDATA_ERROR_OLD;
return;
}else{
reb_binarydata_input_fields(r, inf, warnings);
}
return;
}
struct reb_simulation* reb_simulation_create_from_simulationarchive(struct reb_simulationarchive* sa, int64_t snapshot){
if (sa==NULL) return NULL;
enum REB_BINARYDATA_ERROR_CODE warnings = REB_BINARYDATA_WARNING_NONE;
struct reb_simulation* r = reb_simulation_create();
reb_simulation_init_from_simulationarchive_with_messages(r, sa, snapshot, &warnings);
r = reb_binarydata_process_warnings(r, warnings);
return r; }
struct reb_simulation* reb_simulation_create_from_file(char* filename, int64_t snapshot){
enum REB_BINARYDATA_ERROR_CODE warnings = REB_BINARYDATA_WARNING_NONE;
struct reb_simulation* r = reb_simulation_create();
struct reb_simulationarchive* sa = reb_simulationarchive_create_from_file_with_messages(filename, &warnings);
if (warnings & REB_BINARYDATA_ERROR_NOFILE){
free(sa);
return NULL;
}else{
reb_binarydata_process_warnings(NULL, warnings);
}
reb_simulation_init_from_simulationarchive_with_messages(r, sa, snapshot, &warnings);
if (sa){
reb_simulationarchive_free(sa);
}
r = reb_binarydata_process_warnings(r, warnings);
return r;
}
void reb_simulationarchive_read_from_stream_with_messages(struct reb_simulationarchive* sa, enum REB_BINARYDATA_ERROR_CODE* warnings){
if (sa->inf==NULL){
*warnings |= REB_BINARYDATA_ERROR_NOFILE;
return;
}
fseek(sa->inf, 0, SEEK_SET);
struct reb_binarydata_field field = {0};
sa->version = 0;
double t0 = 0;
sa->reb_version_major = 0;
sa->reb_version_minor = 0;
sa->reb_version_patch = 0;
char name[REB_STRING_SIZE_MAX] = "";
do{
int didReadField = (int)fread(&field,sizeof(struct reb_binarydata_field),1,sa->inf);
if (!didReadField){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
break;
}
if (field.size_name == reb_binarydata_header){
int64_t objects = 0;
const int64_t bufsize = 64 - sizeof(struct reb_binarydata_field);
char readbuf[64], curvbuf[64];
const char* header = "REBOUND Binary File. Version: ";
sprintf(curvbuf,"%s%s",header+sizeof(struct reb_binarydata_field), reb_version_str);
objects += fread(readbuf,sizeof(char),bufsize,sa->inf);
int c1=0, c2=0, c3=0;
for (int c=0; c<bufsize; c++){
if (c2 != 0 && c3 == 0 && readbuf[c] == '.'){
c3 = c;
}
if (c1 != 0 && c2 == 0 && readbuf[c] == '.'){
c2 = c;
}
if (c1 == 0 && readbuf[c] == ':'){
c1 = c;
}
}
if (c1==0 || c2==0 || c3==0){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
}else{
char cpatch[64];
char cminor[64];
char cmajor[64];
strncpy(cpatch, readbuf+c3+1, 3);
cminor[4] = '\0';
strncpy(cminor, readbuf+c2+1, c3-c2-1);
cminor[c3-c2-1] = '\0';
strncpy(cmajor, readbuf+c1+1, c2-c1-1);
cmajor[c2-c1-1] = '\0';
sa->reb_version_patch = atoi(cpatch);
sa->reb_version_minor = atoi(cminor);
sa->reb_version_major = atoi(cmajor);
}
if (objects < 1){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
}else{
if(strncmp(readbuf,curvbuf,bufsize)!=0){
*warnings |= REB_BINARYDATA_WARNING_VERSION;
}
}
continue;
}
if (field.size_name==0 || field.size_name>REB_STRING_SIZE_MAX){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
break;
}
didReadField = (int)fread(name,field.size_name,1,sa->inf);
if (!didReadField){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
break;
}
if (strcmp(name, "t")==0){
fread(&t0, field.size_data, 1, sa->inf);
}else if (strcmp(name, "simulationarchive_version")==0){
fread(&(sa->version), field.size_data, 1, sa->inf);
}else if (strcmp(name, "simulationarchive_auto_walltime")==0){
fread(&(sa->auto_walltime), field.size_data, 1, sa->inf);
}else if (strcmp(name, "simulationarchive_auto_interval")==0){
fread(&(sa->auto_interval), field.size_data, 1, sa->inf);
}else if (strcmp(name, "simulationarchive_auto_step")==0){
fread(&(sa->auto_step), field.size_data, 1, sa->inf);
}else{
fseek(sa->inf,field.size_data,SEEK_CUR);
}
}while(strcmp(name, "end"));
if (sa->version<5){
free(sa->filename);
sa->filename = NULL;
fclose(sa->inf);
sa->inf = NULL;
*warnings |= REB_BINARYDATA_ERROR_OLD;
return;
}else{
int64_t nblobsmax = 1024;
sa->t = calloc(nblobsmax,sizeof(double));
sa->offset = calloc(nblobsmax,sizeof(uint64_t));
fseek(sa->inf, 0, SEEK_SET);
sa->nblobs = 0;
int read_error = 0;
for(int64_t i=0;i<nblobsmax;i++){
struct reb_binarydata_field field = {0};
sa->offset[i] = ftell(sa->inf);
int blob_finished = 0;
do{
size_t r1 = fread(&field,sizeof(struct reb_binarydata_field),1,sa->inf);
if (field.size_name == reb_binarydata_header){
int s1 = fseek(sa->inf,64 - sizeof(struct reb_binarydata_field),SEEK_CUR);
if (s1){
read_error = 1;
}
continue;
}
r1 &= (int)fread(name,field.size_name,1,sa->inf);
if (r1==1){
if (strcmp(name, "t")==0){
size_t r2 = fread(&(sa->t[i]), field.size_data,1,sa->inf);
if (r2!=1){
read_error = 1;
}
}else if (strcmp(name, "end")==0){
blob_finished = 1;
}else{
int s2 = fseek(sa->inf,field.size_data,SEEK_CUR);
if (s2){
read_error = 1;
}
}
}else{
read_error = 1;
}
}while(blob_finished==0 && read_error==0);
if (read_error){
break;
}
struct reb_simulationarchive_blob blob = {0};
size_t r3;
r3 = fread(&blob, sizeof(struct reb_simulationarchive_blob), 1, sa->inf);
int next_blob_is_corrupted = 0;
if (r3!=1){ next_blob_is_corrupted = 1;
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
}
if (i>0){
size_t blobsize;
blobsize = sizeof(struct reb_simulationarchive_blob);
if (((int64_t)blob.offset_prev )+ ((int64_t)blobsize) != ftell(sa->inf) - ((int64_t)sa->offset[i]) ){
read_error = 1;
break;
}
}
sa->nblobs = i+1;
if (blob.offset_next==0 || next_blob_is_corrupted){
break;
}
if (i==nblobsmax-1){ nblobsmax += 1024;
sa->t = realloc(sa->t,sizeof(double)*nblobsmax);
sa->offset = realloc(sa->offset,sizeof(uint64_t)*nblobsmax);
}
}
if (read_error){
if (sa->nblobs>0){
*warnings |= REB_BINARYDATA_WARNING_CORRUPTFILE;
}else{
fclose(sa->inf);
sa->inf = NULL;
free(sa->filename);
sa->filename = NULL;
free(sa->t);
sa->t = NULL;
free(sa->offset);
sa->offset = NULL;
free(sa);
*warnings |= REB_BINARYDATA_ERROR_SEEK;
return;
}
}
}
}
struct reb_simulationarchive* reb_simulationarchive_create_from_file_with_messages(const char* filename, enum REB_BINARYDATA_ERROR_CODE* warnings){
struct reb_simulationarchive* sa = malloc(sizeof(struct reb_simulationarchive));
#ifdef MPI
int initialized;
MPI_Initialized(&initialized);
if (!initialized){
int argc = 0;
char** argv = NULL;
MPI_Init(&argc, &argv);
}
int mpi_id=0;
MPI_Comm_rank(MPI_COMM_WORLD,&mpi_id);
char filename_mpi[REB_STRING_SIZE_MAX];
sprintf(filename_mpi,"%s_%d",filename,mpi_id);
sa->inf = fopen(filename_mpi,"rb");
#else
sa->inf = fopen(filename,"rb");
#endif sa->filename = malloc(strlen(filename)+1);
strcpy(sa->filename,filename);
reb_simulationarchive_read_from_stream_with_messages(sa, warnings);
return sa;
}
struct reb_simulationarchive* reb_simulationarchive_create_from_file(const char* filename){
enum REB_BINARYDATA_ERROR_CODE warnings = REB_BINARYDATA_WARNING_NONE;
struct reb_simulationarchive* sa = reb_simulationarchive_create_from_file_with_messages(filename, &warnings);
if (warnings & REB_BINARYDATA_ERROR_NOFILE){
free(sa);
sa = NULL;
}else{
reb_binarydata_process_warnings(NULL, warnings);
}
return sa;
}
struct reb_simulationarchive* reb_simulationarchive_create_from_buffer_with_messages(char* buffer, size_t size, enum REB_BINARYDATA_ERROR_CODE* warnings){
struct reb_simulationarchive* sa = malloc(sizeof(struct reb_simulationarchive));
sa->inf = reb_fmemopen(buffer, size, "rb");
sa->filename = NULL;
reb_simulationarchive_read_from_stream_with_messages(sa, warnings);
return sa;
}
void reb_simulationarchive_free(struct reb_simulationarchive* sa){
if (sa==NULL) return;
if (sa->inf){
fclose(sa->inf);
}
free(sa->filename);
free(sa->t);
free(sa->offset);
free(sa);
}
void reb_simulationarchive_heartbeat(struct reb_simulation* const r){
if (r->simulationarchive_filename!=NULL){
int modes = 0;
if (r->simulationarchive_auto_interval!=0) modes++;
if (r->simulationarchive_auto_walltime!=0.) modes++;
if (r->simulationarchive_auto_step!=0) modes++;
if (modes>1){
reb_simulation_error(r,"Only use one of simulationarchive_auto_interval, simulationarchive_auto_walltime, or simulationarchive_auto_step");
}
if (r->simulationarchive_auto_interval!=0.){
const double sign = r->dt>0.?1.:-1;
if (sign*r->simulationarchive_next <= sign*r->t){
r->simulationarchive_next += sign*r->simulationarchive_auto_interval;
reb_simulation_save_to_file(r, NULL);
}
}
if (r->simulationarchive_auto_step!=0.){
if (r->simulationarchive_next_step <= r->steps_done){
r->simulationarchive_next_step += r->simulationarchive_auto_step;
reb_simulation_save_to_file(r, NULL);
}
}
if (r->simulationarchive_auto_walltime!=0.){
if (r->simulationarchive_next <= r->walltime){
r->simulationarchive_next += r->simulationarchive_auto_walltime;
reb_simulation_save_to_file(r, NULL);
}
}
}
}
void reb_simulation_save_to_file(struct reb_simulation* const r, const char* filename){
if (r->simulationarchive_version<5){
reb_simulation_error(r, "Writing Simulationarchives with a version < 5 is no longer supported.\n");
return;
}
if (filename==NULL) filename = r->simulationarchive_filename;
struct stat buffer;
#ifdef MPI
#define filename_combined filename_mpi
char filename_mpi[REB_STRING_SIZE_MAX];
sprintf(filename_mpi,"%s_%d",filename,r->mpi_id);
#else
#define filename_combined filename
#endif if (stat(filename_combined, &buffer) < 0){
FILE* of = fopen(filename_combined,"wb");
if (of==NULL){
reb_simulation_error(r, "Can not open file.");
return;
}
char* bufp;
size_t sizep;
reb_binarydata_simulation_to_stream(r, &bufp,&sizep);
fwrite(bufp,sizep,1,of);
free(bufp);
fclose(of);
}else{
FILE* of = fopen(filename_combined,"r+b");
fseek(of, 64, SEEK_SET); struct reb_binarydata_field field = {0};
char name[REB_STRING_SIZE_MAX];
struct reb_simulationarchive_blob blob = {0};
int bytesread;
do{
bytesread = (int)fread(&field,sizeof(struct reb_binarydata_field),1,of);
if (!bytesread){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. A recovery attempt has failed. No snapshot has been saved.\n");
return;
}
if (field.size_name != reb_binarydata_header && (field.size_name==0 || field.size_name>REB_STRING_SIZE_MAX)){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. A recovery attempt has failed. No snapshot has been saved.\n");
return;
}
bytesread = (int)fread(name,field.size_name,1,of);
if (!bytesread){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. A recovery attempt has failed. No snapshot has been saved.\n");
return;
}
int seekfailed = fseek(of, field.size_data, SEEK_CUR);
if (seekfailed){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. A recovery attempt has failed. No snapshot has been saved.\n");
return;
}
}while(strcmp(name, "end"));
int64_t size_old = ftell(of);
bytesread = (int)fread(&blob,sizeof(struct reb_simulationarchive_blob),1,of);
if (bytesread!=1){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. A recovery attempt has failed. No snapshot has been saved.\n");
return;
}
int archive_contains_more_than_one_blob = 0;
if (blob.offset_next>0){
archive_contains_more_than_one_blob = 1;
}
char* buf_old = malloc(size_old);
fseek(of, 0, SEEK_SET);
fread(buf_old, size_old,1,of);
char* buf_new;
size_t size_new;
reb_binarydata_simulation_to_stream(r, &buf_new, &size_new);
char* buf_diff;
size_t size_diff;
reb_binarydata_diff(buf_old, size_old, buf_new, size_new, &buf_diff, &size_diff, REB_BINARYDATA_OUTPUT_STREAM);
int file_corrupt = 0;
int seekfailed = fseek(of, -sizeof(struct reb_simulationarchive_blob), SEEK_END);
if (seekfailed){
file_corrupt = 1;
goto recovery_attempt;
}
bytesread = (int)fread(&blob, sizeof(struct reb_simulationarchive_blob), 1, of);
if (!bytesread){
file_corrupt = 1;
goto recovery_attempt;
}
if ( (archive_contains_more_than_one_blob && blob.offset_prev <=0) || blob.offset_next != 0){ file_corrupt = 1;
goto recovery_attempt;
}
if (file_corrupt==0 && archive_contains_more_than_one_blob ){
size_t end_len = strlen("end")+1;
int seekfailed = fseek(of, - sizeof(struct reb_simulationarchive_blob) - sizeof(struct reb_binarydata_field) - end_len, SEEK_CUR);
if (seekfailed){
file_corrupt = 1;
goto recovery_attempt;
}
bytesread = (int)fread(&field, sizeof(struct reb_binarydata_field), 1, of);
if (!bytesread || field.size_name == 0 || field.size_name > REB_STRING_SIZE_MAX){
file_corrupt = 1;
goto recovery_attempt;
}
bytesread = (int)fread(name, field.size_name, 1, of);
if (!bytesread){
file_corrupt = 1;
goto recovery_attempt;
}
if (strcmp(name, "end") || field.size_data !=0){
file_corrupt = 1;
goto recovery_attempt;
}
seekfailed = fseek(of, -blob.offset_prev - sizeof(struct reb_simulationarchive_blob), SEEK_CUR);
if (seekfailed){
file_corrupt = 1;
goto recovery_attempt;
}
struct reb_simulationarchive_blob blob2 = {0};
bytesread = (int)fread(&blob2, sizeof(struct reb_simulationarchive_blob), 1, of);
if (!bytesread){
file_corrupt = 1;
}
}
recovery_attempt:
if (file_corrupt){
reb_simulation_warning(r, "Simulationarchive appears to be corrupted. REBOUND will attempt to fix it before appending more snapshots.\n");
seekfailed = fseek(of, size_old, SEEK_SET);
int64_t last_blob = size_old + sizeof(struct reb_simulationarchive_blob);
do
{
seekfailed = fseek(of, -sizeof(struct reb_binarydata_field), SEEK_CUR);
if (seekfailed){
break;
}
seekfailed = fseek(of, -sizeof(char)*4, SEEK_CUR); if (seekfailed){
break;
}
bytesread = (int)fread(&field, sizeof(struct reb_binarydata_field), 1, of);
if (field.size_name != 4){ break;
}
if (!bytesread || field.size_name == 0 || (field.size_name > REB_STRING_SIZE_MAX && field.size_name != reb_binarydata_header)){
break;
}
bytesread = (int)fread(name, field.size_name, 1, of);
if (!bytesread || strncmp(name, "end", 4)){ break;
}
bytesread = (int)fread(&blob, sizeof(struct reb_simulationarchive_blob), 1, of);
if (!bytesread){
break;
}
last_blob = ftell(of);
if (blob.offset_next==0){
break;
}
seekfailed = fseek(of, blob.offset_next, SEEK_CUR);
if (seekfailed){
break;
}
} while(1);
fseek(of, last_blob, SEEK_SET);
}else{
fseek(of, 0, SEEK_END);
}
fseek(of, -sizeof(struct reb_simulationarchive_blob), SEEK_CUR);
fread(&blob, sizeof(struct reb_simulationarchive_blob), 1, of);
blob.offset_next = (uint64_t)(size_diff+sizeof(struct reb_binarydata_field)+strlen("end")+1);
fseek(of, -sizeof(struct reb_simulationarchive_blob), SEEK_CUR);
fwrite(&blob, sizeof(struct reb_simulationarchive_blob), 1, of);
fwrite(buf_diff, size_diff, 1, of);
field.size_name = strlen("end")+1;
field.size_data = 0;
fwrite(&field,sizeof(struct reb_binarydata_field), 1, of);
fwrite("end",field.size_name, 1, of);
blob.index++;
blob.offset_prev = blob.offset_next;
blob.offset_next = 0;
fwrite(&blob, sizeof(struct reb_simulationarchive_blob), 1, of);
fclose(of);
free(buf_new);
free(buf_old);
free(buf_diff);
}
}
static int check_and_set_simulationarchive_filename(struct reb_simulation* const r, const char* filename){
if (r==NULL) return -1;
if (filename==NULL){
reb_simulation_error(r, "Filename missing.");
return -1;
}
struct stat buffer;
#ifdef MPI
#define filename_combined filename_mpi
char filename_mpi[REB_STRING_SIZE_MAX];
sprintf(filename_mpi,"%s_%d",filename,r->mpi_id);
#else
#define filename_combined filename
#endif if (stat(filename_combined, &buffer) == 0){
reb_simulation_warning(r, "File in use for Simulationarchive already exists. Snapshots will be appended.");
}
free(r->simulationarchive_filename);
r->simulationarchive_filename = strdup(filename);
return 0;
}
void reb_simulation_save_to_file_interval(struct reb_simulation* const r, const char* filename, double interval){
if(check_and_set_simulationarchive_filename(r,filename)<0) return;
if(r->simulationarchive_auto_interval != interval){
r->simulationarchive_auto_interval = interval;
r->simulationarchive_next = r->t;
}
}
void reb_simulation_save_to_file_walltime(struct reb_simulation* const r, const char* filename, double walltime){
if(check_and_set_simulationarchive_filename(r,filename)<0) return;
r->simulationarchive_auto_walltime = walltime;
r->simulationarchive_next = r->walltime;
}
void reb_simulation_save_to_file_step(struct reb_simulation* const r, const char* filename, uint64_t step){
if(check_and_set_simulationarchive_filename(r,filename)<0) return;
if(r->simulationarchive_auto_step != step){
r->simulationarchive_auto_step = step;
r->simulationarchive_next_step = r->steps_done;
}
}