int str_to_counters(const char *str, char **client, char *status, char *phase, char **path, struct cntr *p1cntr, struct cntr *cntr, char ***backups) { int t=0; char *tok=NULL; char *copy=NULL; reset_filecounter(p1cntr, 0); reset_filecounter(cntr, 0); if(!(copy=strdup(str))) { log_out_of_memory(__FUNCTION__); return -1; } if((tok=strtok(copy, "\t\n"))) { char *counter_version=NULL; if(client && !(*client=strdup(tok))) { log_out_of_memory(__FUNCTION__); return -1; } if(!(counter_version=strtok(NULL, "\t\n"))) { free(copy); return 0; } // First token after the client name is the version of // the counter parser thing, which now has to be noted // because counters might be passed to the client instead // of just the server status monitor. if(*counter_version==COUNTER_VERSION_2 || *counter_version==COUNTER_VERSION_1) // old version { while(1) { int x=1; t++; if(!(tok=strtok(NULL, "\t\n"))) break; if (t==x++) { if(status) *status=*tok; } else if(t==x++) { if(status && (*status==STATUS_IDLE || *status==STATUS_SERVER_CRASHED || *status==STATUS_CLIENT_CRASHED)) { int b=0; if(backups) { // Build a list of backups. do { if(add_to_backup_list(backups, &b, tok)) { free(copy); return -1; } } while((tok=strtok(NULL, "\t\n"))); } } else { if(phase) *phase=*tok; } } else if(t==x++) { extract_ul(tok, &(cntr->total), &(cntr->total_changed), &(cntr->total_same), &(cntr->total_deleted), &(p1cntr->total)); } else if(t==x++) { extract_ul(tok, &(cntr->file), &(cntr->file_changed), &(cntr->file_same), &(cntr->file_deleted), &(p1cntr->file)); } else if(t==x++) { extract_ul(tok, &(cntr->enc), &(cntr->enc_changed), &(cntr->enc_same), &(cntr->enc_deleted), &(p1cntr->enc)); } else if(t==x++) { extract_ul(tok, &(cntr->meta), &(cntr->meta_changed), &(cntr->meta_same), &(cntr->meta_deleted), &(p1cntr->meta)); } else if(t==x++) { extract_ul(tok, &(cntr->encmeta), &(cntr->encmeta_changed), &(cntr->encmeta_same), &(cntr->encmeta_deleted), &(p1cntr->encmeta)); } else if(t==x++) { extract_ul(tok, &(cntr->dir), &(cntr->dir_changed), &(cntr->dir_same), &(cntr->dir_deleted), &(p1cntr->dir)); } else if(t==x++) { extract_ul(tok, &(cntr->slink), &(cntr->slink_changed), &(cntr->slink_same), &(cntr->slink_deleted), &(p1cntr->slink)); } else if(t==x++) { extract_ul(tok, &(cntr->hlink), &(cntr->hlink_changed), &(cntr->hlink_same), &(cntr->hlink_deleted), &(p1cntr->hlink)); } else if(t==x++) { extract_ul(tok, &(cntr->special), &(cntr->special_changed), &(cntr->special_same), &(cntr->special_deleted), &(p1cntr->special)); } else if(*counter_version==COUNTER_VERSION_2 && t==x++) { extract_ul(tok, &(cntr->vss), &(cntr->vss_changed), &(cntr->vss_same), &(cntr->vss_deleted), &(p1cntr->vss)); } else if(*counter_version==COUNTER_VERSION_2 && t==x++) { extract_ul(tok, &(cntr->encvss), &(cntr->encvss_changed), &(cntr->encvss_same), &(cntr->encvss_deleted), &(p1cntr->encvss)); } else if(*counter_version==COUNTER_VERSION_2 && t==x++) { extract_ul(tok, &(cntr->vss_t), &(cntr->vss_t_changed), &(cntr->vss_t_same), &(cntr->vss_t_deleted), &(p1cntr->vss_t)); } else if(*counter_version==COUNTER_VERSION_2 && t==x++) { extract_ul(tok, &(cntr->encvss_t), &(cntr->encvss_t_changed), &(cntr->encvss_t_same), &(cntr->encvss_t_deleted), &(p1cntr->encvss_t)); } else if(t==x++) { extract_ul(tok, &(cntr->gtotal), &(cntr->gtotal_changed), &(cntr->gtotal_same), &(cntr->gtotal_deleted), &(p1cntr->gtotal)); } else if(t==x++) { cntr->warning= strtoull(tok, NULL, 10); } else if(t==x++) { p1cntr->byte= strtoull(tok, NULL, 10); } else if(t==x++) { cntr->byte= strtoull(tok, NULL, 10); } else if(t==x++) { cntr->recvbyte= strtoull(tok, NULL, 10); } else if(t==x++) { cntr->sentbyte= strtoull(tok, NULL, 10); } else if(t==x++) { p1cntr->start=atol(tok); } else if(t==x++) { if(path && !(*path=strdup(tok))) { log_out_of_memory(__FUNCTION__); return -1; } } } } } free(copy); return 0; }
/* May return 1 to mean try again. This happens after a successful certificate signing request so that it connects again straight away with the new key/certificate. Returns 2 if there were restore/verify warnings. Returns 3 if timer conditions were not met. */ static int do_client(struct config *conf, enum action act, int vss_restore, int json) { int ret=0; int rfd=-1; int resume=0; SSL *ssl=NULL; BIO *sbio=NULL; char buf[256]=""; SSL_CTX *ctx=NULL; struct cntr cntr; struct cntr p1cntr; char *incexc=NULL; char *server_version=NULL; const char *phase1str="backupphase1"; reset_filecounter(&p1cntr, time(NULL)); reset_filecounter(&cntr, time(NULL)); setup_signals_client(); // settimers(0, 100); logp("begin client\n"); if(act!=ACTION_ESTIMATE) { ssl_load_globals(); if(!(ctx=ssl_initialise_ctx(conf))) { logp("error initialising ssl ctx\n"); ret=-1; goto end; } SSL_CTX_set_session_id_context(ctx, (const unsigned char *)&s_server_session_id_context, sizeof(s_server_session_id_context)); if((rfd=init_client_socket(conf->server, conf->port))<0) { ret=-1; goto end; } if(!(ssl=SSL_new(ctx)) || !(sbio=BIO_new_socket(rfd, BIO_NOCLOSE))) { ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); logp("Problem joining SSL to the socket: %s\n", buf); ret=-1; goto end; } SSL_set_bio(ssl, sbio, sbio); if(SSL_connect(ssl)<=0) { ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); logp("SSL connect error: %s\n", buf); ret=-1; goto end; } } if((ret=async_init(rfd, ssl, conf, act==ACTION_ESTIMATE))) goto end; // Set quality of service bits on backup packets. if(act==ACTION_BACKUP || act==ACTION_BACKUP_TIMED) set_bulk_packets(); if(act!=ACTION_ESTIMATE) { char cmd=0; size_t len=0; char *feat=NULL; int ca_ret=0; if((ret=authorise_client(conf, &server_version, &p1cntr))) goto end; if(server_version) { logp("Server version: %s\n", server_version); // Servers before 1.3.2 did not tell us their versions. // 1.3.2 and above can do the automatic CA stuff that // follows. if((ca_ret=ca_client_setup(conf, &p1cntr))<0) { // Error logp("Error with certificate signing request\n"); ret=-1; goto end; } else if(ca_ret>0) { // Certificate signed successfully. // Everything is OK, but we will reconnect now, in // order to use the new keys/certificates. ret=1; goto end; } } set_non_blocking(rfd); if((ret=ssl_check_cert(ssl, conf))) { logp("check cert failed\n"); goto end; } if((ret=async_write_str(CMD_GEN, "extra_comms_begin"))) { logp("Problem requesting extra_comms_begin\n"); goto end; } // Servers greater than 1.3.0 will list the extra_comms // features they support. else if((ret=async_read(&cmd, &feat, &len))) { logp("Problem reading response to extra_comms_begin\n"); goto end; } else if(cmd!=CMD_GEN) { logp("Unexpected command from server when reading response to extra_comms_begin: %c:%s\n", cmd, feat); ret=-1; goto end; } else if(strncmp(feat, "extra_comms_begin ok", strlen("extra_comms_begin ok"))) { logp("Unexpected response from server when reading response to extra_comms_begin: %c:%s\n", cmd, feat); ret=-1; goto end; } // Can add extra bits here. The first extra bit is the // autoupgrade stuff. if(server_supports_autoupgrade(feat)) { if(conf->autoupgrade_dir && conf->autoupgrade_os && (ret=autoupgrade_client(conf, &p1cntr))) goto end; } // :srestore: means that the server wants to do a restore. if(server_supports(feat, ":srestore:")) { if(conf->server_can_restore) { logp("Server is initiating a restore\n"); if(incexc) { free(incexc); incexc=NULL; } if((ret=incexc_recv_client_restore(&incexc, conf, &p1cntr))) goto end; if(incexc) { if((ret=parse_incexcs_buf(conf, incexc))) goto end; act=ACTION_RESTORE; log_restore_settings(conf, 1); } } else { logp("Server wants to initiate a restore\n"); logp("Client configuration says no\n"); if(async_write_str(CMD_GEN, "srestore not ok")) { ret=-1; goto end; } } } if(conf->orig_client) { char str[512]=""; snprintf(str, sizeof(str), "orig_client=%s", conf->orig_client); if(!server_supports(feat, ":orig_client:")) { logp("Server does not support switching client.\n"); ret=-1; goto end; } if((ret=async_write_str(CMD_GEN, str)) || (ret=async_read_expect(CMD_GEN, "orig_client ok"))) { logp("Problem requesting %s\n", str); ret=-1; goto end; } logp("Switched to client %s\n", conf->orig_client); } // :sincexc: is for the server giving the client the // incexc config. if(act==ACTION_BACKUP || act==ACTION_BACKUP_TIMED) { if(!incexc && server_supports(feat, ":sincexc:")) { logp("Server is setting includes/excludes.\n"); if(incexc) { free(incexc); incexc=NULL; } if((ret=incexc_recv_client(&incexc, conf, &p1cntr))) goto end; if(incexc && (ret=parse_incexcs_buf(conf, incexc))) goto end; } } if(server_supports(feat, ":counters:")) { if(async_write_str(CMD_GEN, "countersok")) goto end; conf->send_client_counters=1; } // :incexc: is for the client sending the server the // incexc config so that it better knows what to do on // resume. if(server_supports(feat, ":incexc:") && (ret=incexc_send_client(conf, &p1cntr))) goto end; if((ret=async_write_str(CMD_GEN, "extra_comms_end")) || (ret=async_read_expect(CMD_GEN, "extra_comms_end ok"))) { logp("Problem requesting extra_comms_end\n"); goto end; } if(feat) free(feat); } rfd=-1; switch(act) { case ACTION_BACKUP_TIMED: phase1str="backupphase1timed"; case ACTION_BACKUP: { // Set bulk packets quality of service flags on backup. if(incexc) { logp("Server is overriding the configuration\n"); logp("with the following settings:\n"); if(log_incexcs_buf(incexc)) { ret=-1; goto end; } } if(!conf->sdcount) { logp("Found no include paths!\n"); ret=-1; goto end; } if(!(ret=maybe_check_timer(phase1str, conf, &resume))) { if(conf->backup_script_pre) { int a=0; const char *args[12]; args[a++]=conf->backup_script_pre; args[a++]="pre"; args[a++]="reserved2"; args[a++]="reserved3"; args[a++]="reserved4"; args[a++]="reserved5"; args[a++]=NULL; if(run_script(args, conf->backup_script_pre_arg, conf->bprecount, &p1cntr, 1, 1)) ret=-1; } if(!ret && do_backup_client(conf, resume, 0, &p1cntr, &cntr)) ret=-1; if((conf->backup_script_post_run_on_fail || !ret) && conf->backup_script_post) { int a=0; const char *args[12]; args[a++]=conf->backup_script_post; args[a++]="post"; // Tell post script whether the restore // failed. args[a++]=ret?"1":"0"; args[a++]="reserved3"; args[a++]="reserved4"; args[a++]="reserved5"; args[a++]=NULL; if(run_script(args, conf->backup_script_post_arg, conf->bpostcount, &cntr, 1, 1)) ret=-1; } } if(ret<0) logp("error in backup\n"); else if(ret>0) { // Timer script said no. // Have a distinct return value to // differentiate between other cases // (ssl reconnection and restore/verify // warnings). ret=3; } else logp("backup finished ok\n"); break; } case ACTION_RESTORE: case ACTION_VERIFY: { if(conf->restore_script_pre) { int a=0; const char *args[12]; args[a++]=conf->restore_script_pre; args[a++]="pre"; args[a++]="reserved2"; args[a++]="reserved3"; args[a++]="reserved4"; args[a++]="reserved5"; args[a++]=NULL; if(run_script(args, conf->restore_script_pre_arg, conf->rprecount, &cntr, 1, 1)) ret=-1; } if(!ret && do_restore_client(conf, act, vss_restore, &p1cntr, &cntr)) ret=-1; if((conf->restore_script_post_run_on_fail || !ret) && conf->restore_script_post) { int a=0; const char *args[12]; args[a++]=conf->restore_script_pre; args[a++]="post"; // Tell post script whether the restore // failed. args[a++]=ret?"1":"0"; args[a++]="reserved3"; args[a++]="reserved4"; args[a++]="reserved5"; args[a++]=NULL; if(run_script(args, conf->restore_script_post_arg, conf->rpostcount, &cntr, 1, 1)) ret=-1; } // Return non-zero if there were warnings, // so that the test script can easily check. if(p1cntr.warning+cntr.warning) ret=2; break; } case ACTION_ESTIMATE: if(!ret) ret=do_backup_client(conf, 0, 1, &p1cntr, &cntr); break; case ACTION_DELETE: if(!ret) ret=do_delete_client(conf); break; case ACTION_LIST: case ACTION_LONG_LIST: default: ret=do_list_client(conf, act, json); break; } end: close_fd(&rfd); async_free(); if(act!=ACTION_ESTIMATE) ssl_destroy_ctx(ctx); if(incexc) free(incexc); if(server_version) free(server_version); //logp("end client\n"); return ret; }
// a = length of struct bu array // i = position to restore from static int restore_manifest(struct bu *arr, int a, int i, const char *tmppath1, const char *tmppath2, regex_t *regex, int srestore, enum action act, const char *client, char **dir_for_notify, struct cntr *p1cntr, struct cntr *cntr, struct config *cconf) { int ret=0; gzFile zp=NULL; char *manifest=NULL; char *datadir=NULL; FILE *logfp=NULL; char *logpath=NULL; char *logpathz=NULL; // For sending status information up to the server. char status=STATUS_RESTORING; if(act==ACTION_RESTORE) status=STATUS_RESTORING; else if(act==ACTION_VERIFY) status=STATUS_VERIFYING; if( (act==ACTION_RESTORE && !(logpath=prepend_s(arr[i].path, "restorelog", strlen("restorelog")))) || (act==ACTION_RESTORE && !(logpathz=prepend_s(arr[i].path, "restorelog.gz", strlen("restorelog.gz")))) || (act==ACTION_VERIFY && !(logpath=prepend_s(arr[i].path, "verifylog", strlen("verifylog")))) || (act==ACTION_VERIFY && !(logpathz=prepend_s(arr[i].path, "verifylog.gz", strlen("verifylog.gz")))) || !(manifest=prepend_s(arr[i].path, "manifest.gz", strlen("manifest.gz")))) { log_and_send_oom(__FUNCTION__); ret=-1; } else if(!(logfp=open_file(logpath, "ab")) || set_logfp(logfp, cconf)) { char msg[256]=""; snprintf(msg, sizeof(msg), "could not open log file: %s", logpath); log_and_send(msg); ret=-1; } *dir_for_notify=strdup(arr[i].path); log_restore_settings(cconf, srestore); // First, do a pass through the manifest to set up the counters. // This is the equivalent of a phase1 scan during backup. if(!ret && !(zp=gzopen_file(manifest, "rb"))) { log_and_send("could not open manifest"); ret=-1; } else { int ars=0; int quit=0; struct sbuf sb; init_sbuf(&sb); while(!quit) { if((ars=sbuf_fill(NULL, zp, &sb, cntr))) { if(ars<0) ret=-1; // ars==1 means end ok quit++; } else { if((!srestore || check_srestore(cconf, sb.path)) && check_regex(regex, sb.path)) { do_filecounter(p1cntr, sb.cmd, 0); if(sb.endfile) do_filecounter_bytes(p1cntr, strtoull(sb.endfile, NULL, 10)); /* if(sb.cmd==CMD_FILE || sb.cmd==CMD_ENC_FILE || sb.cmd==CMD_METADATA || sb.cmd==CMD_ENC_METADATA || sb.cmd==CMD_VSS || sb.cmd==CMD_ENC_VSS || sb.cmd==CMD_VSS_T || sb.cmd==CMD_ENC_VSS_T || sb.cmd==CMD_EFS_FILE) do_filecounter_bytes(p1cntr, (unsigned long long) sb.statp.st_size); */ } } free_sbuf(&sb); } free_sbuf(&sb); gzclose_fp(&zp); } if(cconf->send_client_counters) { if(send_counters(client, p1cntr, cntr)) { ret=-1; } } // Now, do the actual restore. if(!ret && !(zp=gzopen_file(manifest, "rb"))) { log_and_send("could not open manifest"); ret=-1; } else { char cmd; int s=0; int quit=0; size_t len=0; struct sbuf sb; // For out-of-sequence directory restoring so that the // timestamps come out right: int scount=0; struct sbuf **sblist=NULL; init_sbuf(&sb); while(!quit) { int ars=0; char *buf=NULL; if(async_read_quick(&cmd, &buf, &len)) { logp("read quick error\n"); ret=-1; quit++; break; } if(buf) { //logp("got read quick\n"); if(cmd==CMD_WARNING) { logp("WARNING: %s\n", buf); do_filecounter(cntr, cmd, 0); free(buf); buf=NULL; continue; } else if(cmd==CMD_INTERRUPT) { // Client wanted to interrupt the // sending of a file. But if we are // here, we have already moved on. // Ignore. free(buf); buf=NULL; continue; } else { logp("unexpected cmd from client: %c:%s\n", cmd, buf); free(buf); buf=NULL; ret=-1; quit++; break; } } if((ars=sbuf_fill(NULL, zp, &sb, cntr))) { if(ars<0) ret=-1; // ars==1 means end ok quit++; } else { if((!srestore || check_srestore(cconf, sb.path)) && check_regex(regex, sb.path) && restore_ent(client, &sb, &sblist, &scount, arr, a, i, tmppath1, tmppath2, act, status, cconf, cntr, p1cntr)) { ret=-1; quit++; } } free_sbuf(&sb); } gzclose_fp(&zp); // Restore any directories that are left in the list. if(!ret) for(s=scount-1; s>=0; s--) { if(restore_sbuf(sblist[s], arr, a, i, tmppath1, tmppath2, act, client, status, p1cntr, cntr, cconf)) { ret=-1; break; } } free_sbufs(sblist, scount); if(!ret) ret=do_restore_end(act, cntr); //print_endcounter(cntr); print_filecounters(p1cntr, cntr, act); reset_filecounter(p1cntr, time(NULL)); reset_filecounter(cntr, time(NULL)); } set_logfp(NULL, cconf); compress_file(logpath, logpathz, cconf); if(manifest) free(manifest); if(datadir) free(datadir); if(logpath) free(logpath); if(logpathz) free(logpathz); return ret; }
// a = length of struct bu array // i = position to restore from static int restore_manifest(struct bu *arr, int a, int i, const char *tmppath1, const char *tmppath2, regex_t *regex, enum action act, const char *client, struct cntr *p1cntr, struct cntr *cntr, struct config *cconf, bool all) { int ret=0; gzFile zp=NULL; char *manifest=NULL; char *datadir=NULL; FILE *logfp=NULL; char *logpath=NULL; char *logpathz=NULL; // For sending status information up to the server. char status=STATUS_RESTORING; if(act==ACTION_RESTORE) status=STATUS_RESTORING; else if(act==ACTION_VERIFY) status=STATUS_VERIFYING; if( (act==ACTION_RESTORE && !(logpath=prepend_s(arr[i].path, "restorelog", strlen("restorelog")))) || (act==ACTION_RESTORE && !(logpathz=prepend_s(arr[i].path, "restorelog.gz", strlen("restorelog.gz")))) || (act==ACTION_VERIFY && !(logpath=prepend_s(arr[i].path, "verifylog", strlen("verifylog")))) || (act==ACTION_VERIFY && !(logpathz=prepend_s(arr[i].path, "verifylog.gz", strlen("verifylog.gz")))) || !(manifest=prepend_s(arr[i].path, "manifest.gz", strlen("manifest.gz")))) { log_and_send("out of memory"); ret=-1; } else if(!(logfp=open_file(logpath, "ab")) || set_logfp(logfp)) { char msg[256]=""; snprintf(msg, sizeof(msg), "could not open log file: %s", logpath); log_and_send(msg); ret=-1; } else if(!(zp=gzopen_file(manifest, "rb"))) { log_and_send("could not open manifest"); ret=-1; } else { char cmd; int quit=0; size_t len=0; struct sbuf sb; // For out-of-sequence directory restoring so that the // timestamps come out right: int s=0; int scount=0; struct sbuf **sblist=NULL; init_sbuf(&sb); while(!quit) { int ars=0; char *buf=NULL; if(async_read_quick(&cmd, &buf, &len)) { logp("read quick error\n"); ret=-1; quit++; break; } if(buf) { //logp("got read quick\n"); if(cmd==CMD_WARNING) { logp("WARNING: %s\n", buf); do_filecounter(cntr, cmd, 0); free(buf); buf=NULL; continue; } else if(cmd==CMD_INTERRUPT) { // Client wanted to interrupt the // sending of a file. But if we are // here, we have already moved on. // Ignore. free(buf); buf=NULL; continue; } else { logp("unexpected cmd from client: %c:%s\n", cmd, buf); free(buf); buf=NULL; ret=-1; quit++; break; } } if((ars=sbuf_fill(NULL, zp, &sb, cntr))) { if(ars<0) ret=-1; // ars==1 means end ok quit++; } else { if(check_regex(regex, sb.path)) { // Check if we have any directories waiting // to be restored. for(s=scount-1; s>=0; s--) { if(is_subdir(sblist[s]->path, sb.path)) { // We are still in a subdir. //printf(" subdir (%s %s)\n", sblist[s]->path, sb.path); break; } else { // Can now restore sblist[s] // because nothing else is // fiddling in a subdirectory. if(restore_sbuf(sblist[s], arr, a, i, tmppath1, tmppath2, act, client, status, p1cntr, cntr, cconf)) { ret=-1; quit++; break; } else if(del_from_sbuf_arr( &sblist, &scount)) { ret=-1; quit++; break; } } } /* If it is a directory, need to remember it and restore it later, so that the permissions come out right. */ /* Meta data of directories will also have the stat stuff set to be a directory, so will also come out at the end. */ if(!ret && S_ISDIR(sb.statp.st_mode)) { if(add_to_sbuf_arr(&sblist, &sb, &scount)) { ret=-1; quit++; } // Wipe out sb, without freeing up // all the strings inside it, which // have been added to sblist. init_sbuf(&sb); } else if(!ret && restore_sbuf(&sb, arr, a, i, tmppath1, tmppath2, act, client, status, p1cntr, cntr, cconf)) { ret=-1; quit++; } } } free_sbuf(&sb); } gzclose_fp(&zp); // Restore any directories that are left in the list. if(!ret) for(s=scount-1; s>=0; s--) { if(restore_sbuf(sblist[s], arr, a, i, tmppath1, tmppath2, act, client, status, p1cntr, cntr, cconf)) { ret=-1; break; } } free_sbufs(sblist, scount); if(!ret && !all) ret=do_restore_end(act, cntr); print_endcounter(cntr); print_filecounters(p1cntr, cntr, act, 0); reset_filecounter(p1cntr); reset_filecounter(cntr); } set_logfp(NULL); compress_file(logpath, logpathz, cconf); if(manifest) free(manifest); if(datadir) free(datadir); if(logpath) free(logpath); if(logpathz) free(logpathz); return ret; }