Newer
Older
backup-commander / html / svc / main.php
  1. <?php
  2.  
  3. $debug_level = 0;
  4. const DEBUG_LOW = 0;
  5. const DEBUG_MED = DEBUG_LOW+1;
  6. const DEBUG_HIGH = DEBUG_MED+1;
  7. const DEBUG_VHIGH = DEBUG_HIGH+1;
  8.  
  9. $log_print_file = __DIR__."/../data/rpc_op.log";
  10.  
  11. function log_print( $msg ){
  12. global $log_print_file;
  13. if($log_print_file==''){
  14. print($msg);
  15. return;
  16. }
  17. file_put_contents( $log_print_file, $msg, FILE_APPEND);
  18. }
  19.  
  20. function debug_print( $msg, $dl=0 ){
  21. global $debug_level;
  22. if($dl>$debug_level) return;
  23. log_print( $msg );
  24. }
  25.  
  26. function debug_println( $msg, $dl=0 ){
  27. global $debug_level;
  28. if($dl>$debug_level) return;
  29. debug_print( date_format( date_create("now", new DateTimeZone("UTC")), 'Y-m-d H:i:s') . " $msg\n", $dl );
  30. }
  31.  
  32. //$argv[0] is always the name that was used to run the script.
  33. if(count($argv)>1){
  34.  
  35. //do not output to service log file
  36. $log_print_file=null;
  37. include __DIR__."/main-svc.php";
  38. if($argv[1]==='stop'){
  39. debug_println( "stopping" );
  40. $rtn = run_main_command( 'f:cli-stop' );
  41. print_r( $rtn );
  42. exit(0);
  43. }
  44. if($argv[1]==='reload'){
  45. debug_println( "reloading" );
  46. $rtn = run_main_command( 'f:cli-reload' );
  47. print_r( $rtn );
  48. exit(0);
  49. }
  50. if($argv[1]==='debug_inc'){
  51. debug_println( "debug_inc" );
  52. run_main_command( 'f:debug_inc' );
  53. exit(0);
  54. }
  55.  
  56. if($argv[1]==='debug_dec'){
  57. debug_println( "debug_dec" );
  58. run_main_command( 'f:debug_dec' );
  59. exit(0);
  60. }
  61.  
  62. $arg_copy = $argv;
  63. array_shift($arg_copy);
  64. $rtn = run_main_command( $arg_copy );
  65. print_r( $rtn );
  66. exit(0);
  67.  
  68. }
  69.  
  70. $g_shutdown = false;
  71.  
  72. /*
  73. * This is the backup process CLI application.
  74. *
  75. * Rather than use 2 threads, I've decided to poll. This is because the threading in PHP is no longer supported.
  76. * Instead we should use parallel:
  77. * https://www.php.net/manual/en/parallel.run.php
  78. * which I don't like. Basically, it doesn't look like you can share code (by way of a reference)?!!
  79. *
  80. * So we are polling the RPC layer every 0.1secs. In practice, this will be plenty for the backup app.
  81. *
  82. */
  83. include __DIR__."/../lib/sysVcom.php";
  84.  
  85.  
  86. debug_println("started");
  87.  
  88. //create the comms file
  89. touch('BU-commander');
  90.  
  91. //remove all queues at startup
  92. clear_IPC( '../svc/BU-commander' );
  93.  
  94. rpc_setGlobals( 'procName', 'BU-commander' );
  95. rpc_setGlobals( 'max_chan', 8182 );
  96. rpc_setGlobals( 'main_pid', getmypid() );
  97.  
  98. print("rpc using ". __DIR__ . "/$g_procName\n" );
  99.  
  100. // problem is that the paths are not correct wrt this location, so we need to adjust them
  101. include __DIR__."/../inc/paths.php";
  102. $g_database_path = '../' . $g_database_path;
  103.  
  104. $g_data_dir = realpath( __DIR__."/../data" );
  105.  
  106. include "db-reads.php";
  107. include "db-writes.php";
  108. include "file-line-reader.php";
  109. include "table-cache.php";
  110.  
  111. $g_bu_states = null;
  112.  
  113.  
  114. // maximum number of forks allowed
  115. $g_fork_max = 4;
  116. // currently running fork count
  117. $g_fork_cnt=0;
  118. //a queue of backup IDs waiting to commence
  119. $g_bu_queue = array();
  120.  
  121. function isQueued( $buid ){
  122. global $g_bu_queue;
  123. foreach( $g_bu_queue as $q ){
  124. if($q==$buid) return true;
  125. }
  126. return false;
  127. }
  128.  
  129. while(true){
  130.  
  131. if($g_shutdown){
  132. print("CLI request to shutdown: googbye!\n");
  133. exit(0);
  134. }
  135.  
  136. $bDidWork = false;
  137. $bDidWork |= check_rpc_clients();
  138. $bDidWork |= check_bu_sched();
  139. $bDidWork |= check_bu_queue();
  140. if(!$bDidWork){
  141. time_nanosleep( 0, 100000000);
  142. }
  143. }
  144.  
  145. /**
  146. * Return true if the backup was kicked off
  147. */
  148. function check_bu_sched(){
  149. global $g_bu_table;
  150. global $g_bu_colIdxs;
  151. //get the first non null entry which is not running
  152. $idfound = 0;
  153. $row = null;
  154. for( $i=0; $i<count($g_bu_table); $i++){
  155. $row = $g_bu_table[$i];
  156. if( $row[ $g_bu_colIdxs['BU_DATE'] ] == '' ) continue; //skip anything without a date
  157. if( $row[ $g_bu_colIdxs['BuRunning'] ] == 1 ) continue; //skip any already running
  158. if( $row[ $g_bu_colIdxs['BuError'] ] != null ) continue; //skip entries with problems
  159. if( isQueued( $row[ $g_bu_colIdxs['BUID'] ] ) ) continue; //skip entries already queued
  160. $idfound = $row[ $g_bu_colIdxs['BUID'] ];
  161. break;
  162. }
  163. if($idfound==0) return;
  164. // is it time...?
  165. $str_bu_dt = $row[ $g_bu_colIdxs['BU_DATE']] . ' ' . $row[ $g_bu_colIdxs['BU_TIME']] . ':00';
  166. $schedTime = new DateTime( $str_bu_dt, new DateTimeZone("UTC") );
  167. if( $schedTime > new DateTime('now', new DateTimeZone("UTC") ) ){
  168. //...no it's not
  169. return;
  170. }
  171. [ $res, $state ] = queue_backup( $idfound );
  172.  
  173. return $state!=='queued'; //return true if something some work was started
  174. }
  175.  
  176. function check_bu_queue(){
  177. $res = dequeue_backup();
  178. if($res==null) return false;
  179. if($res[1]=='queued') return false;
  180. return true;
  181. }
  182.  
  183. /**
  184. * Adds a backup ID to the queue. It is kicked off immediately if a slot is available.
  185. */
  186. function queue_backup( $buid, $scheduled = true, $bu_test = false ){
  187. global $g_bu_queue;
  188.  
  189. debug_println("(queue_backup) queue backup($buid)", DEBUG_MED );
  190.  
  191. array_push( $g_bu_queue, array($buid, $scheduled, $bu_test) );
  192. $res = dequeue_backup( );
  193. if($res!=null){
  194. return $res; //either running or error.
  195. }
  196. debug_println("(queue_backup) backup($buid) in queue", DEBUG_MED );
  197. return array("ok:", "queued" ); //will be run later
  198. }
  199.  
  200. /**
  201. * Removes the next back up item from the front of the queue and kicks it off if there are enough
  202. * free forks. Returns the result of the kick-off or null if nothing to do.
  203. */
  204. function dequeue_backup( ){
  205.  
  206. global $g_bu_queue;
  207. global $g_fork_cnt;
  208. global $g_fork_max;
  209.  
  210. if( count($g_bu_queue)==0){
  211. return null; //nothing to do
  212. }
  213. if($g_fork_cnt>=$g_fork_max){
  214. //There are no available forks
  215. return null;
  216. }
  217. $bu_item = array_shift( $g_bu_queue );
  218. debug_println("(dequeue_backup) dequeued backup({$bu_item[0]})", DEBUG_MED );
  219. return kickoff_backup( $bu_item );
  220. }
  221.  
  222. /**
  223. * Kicks off a back up. Returns immediately. Deligates the work to a client fork.
  224. *
  225. * $scheduled:
  226. * true if it is run according to the $schedule info in the db
  227. * false if it has been manually initiated
  228. */
  229. function kickoff_backup( $bu_item ){
  230. [$buid, $scheduled, $bu_test] = $bu_item;
  231. debug_println("(kickoff_backup) backup($buid) to run now", DEBUG_MED );
  232. global $g_bu_table;
  233. global $g_bu_colIdxs;
  234. global $g_fork_cnt;
  235.  
  236. $row = &find_rec_by_id( $buid );
  237. $isRunning = $row[ $g_bu_colIdxs['BuRunning'] ];
  238. if( !$bu_test && $isRunning==1) return array("e:Error", "Backup already running" );
  239. $i=0;
  240. $bu_row[$i++] = $row[ $g_bu_colIdxs['BUName'] ];
  241. $bu_row[$i++] = $row[ $g_bu_colIdxs['Dir_Src'] ];
  242. $bu_row[$i++] = $row[ $g_bu_colIdxs['Dir_Dest'] ];
  243. $bu_row[$i++] = $row[ $g_bu_colIdxs['Files_Ex'] ];
  244. $bu_row[$i++] = $row[ $g_bu_colIdxs['BuType'] ];
  245. $bu_row[$i++] = $isRunning;
  246. $bu_row[$i++] = $row[ $g_bu_colIdxs['Res_User'] ];
  247. $bu_row[$i++] = $row[ $g_bu_colIdxs['Res_Pwd'] ];
  248. // we should be able to use pcntl_fork()
  249. $pid = pcntl_fork();
  250. if ( $pid == -1 ) {
  251. debug_println("fork failed");
  252. return array("e:Error", "fork failed" );
  253. }
  254.  
  255. if ( $pid ) {
  256. // parent process
  257. debug_println( "(kickoff_backup) bu($buid) running, parent(". getmypid().")", DEBUG_MED );
  258. set_bu_running_db( $buid, true );
  259. $row[ $g_bu_colIdxs['BuRunning'] ] = 1;
  260. return array("ok:", "running" );
  261. } else {
  262. // child process
  263. $g_fork_cnt++;
  264.  
  265. $g_bu_table = array(); //for safety
  266. $pid = getmypid();
  267. debug_println( "child($pid) running backup($buid)" );
  268.  
  269. include_once __DIR__."/backup.php";
  270. $bu_result = '';
  271. global $g_data_dir;
  272. $g_data_dir = __DIR__."/../data";
  273. $bu_result = run_backup_block( $buid, $bu_row, $bu_test );
  274. debug_println( "child($pid) finished bu($buid)" );
  275. $obj = rpc( 'm:fork-complete', $pid, "backup-complete", $buid, $scheduled ); //we use our rpc mechanism to notify parent
  276. //print_r( $obj );
  277. //print( "\n" );
  278. exit(0);
  279. }
  280. }
  281.  
  282.  
  283.  
  284. $msgArr = null;
  285.  
  286. /**
  287. * Return true if a request was found and handled
  288. */
  289. function check_rpc_clients(){
  290. global $msgArr;
  291. global $g_fork_cnt;
  292.  
  293. $caller_id = 0;
  294. $obj_in = rpc_listen( $caller_id, $msgArr, false );
  295. $obj_out = null;
  296.  
  297. if($caller_id==0) return false; //nothing to do
  298. try{
  299. if( !is_array($obj_in[0])) {
  300. debug_println("req {$obj_in[0]}", DEBUG_MED );
  301. }
  302. $obj_out = handle_rpc_req( $obj_in );
  303. if($obj_out==null) $obj_out = array('ok:'); //generic return value
  304. }catch( Exception $e ){
  305. debug_println( "Exception: " . $e->getMessage() );
  306. $obj_out = array( 'e:Exception', $e );
  307. }
  308. if( !is_array($obj_out[0])) {
  309. debug_println("reply {$obj_out[0]}", DEBUG_MED );
  310. }
  311. //send msg back
  312. rpc_reply( $caller_id, $obj_out );
  313.  
  314. if( $obj_in[0] === 'm:fork-complete' ){
  315. //clean up child process
  316. debug_println("wait for child({$obj_in[1]})", DEBUG_MED );
  317. $status;
  318. pcntl_waitpid( $obj_in[1], $status );
  319.  
  320. $g_fork_cnt--;
  321. debug_println("child({$obj_in[1]}) complete.", DEBUG_MED );
  322.  
  323. //finish any requirements for specific child processes (db updates etc)
  324. if( count($obj_in)>2 && $obj_in[2]="backup-complete"){
  325. backupComplete( $obj_in[3], $obj_in[4] );
  326. }
  327. }
  328. return true;
  329. }
  330.  
  331.  
  332. /**
  333. * Called by the parent after a client notified that a backup completed.
  334. */
  335. function backupComplete( $buid, $scheduled ){
  336. global $g_bu_colIdxs;
  337. //reload the table data (in case child modified error field)
  338. $row = reload_cached_rec( $buid );
  339.  
  340. if($scheduled){
  341. //if DB error, do nothing
  342. $err = $row[ $g_bu_colIdxs['BuError'] ];
  343. if($err==''){
  344. //update date and time according to the repeat field.
  345. $date = date_create( $row[ $g_bu_colIdxs['BU_DATE'] ] );
  346. $dt_new = null;
  347. switch( $row[ $g_bu_colIdxs['BU_REPEAT'] ] ){
  348. case 'M':
  349. //monthly
  350. date_add( $date, new DateInterval('P1M') );
  351. $dt_new = date_format( $date, "Y-m-d");
  352. break;
  353. case 'W':
  354. //weekly
  355. date_add( $date, new DateInterval('P1W') );
  356. $dt_new = date_format( $date, "Y-m-d");
  357. break;
  358. case 'D':
  359. //daily
  360. date_add( $date, new DateInterval('P1D') );
  361. $dt_new = date_format( $date, "Y-m-d");
  362. break;
  363. }
  364. $row[ $g_bu_colIdxs['BU_DATE'] ] = $dt_new;
  365.  
  366. set_sched_date_db( $buid, $dt_new );
  367. }
  368.  
  369. }
  370. set_bu_running_db( $buid, false );
  371.  
  372. //after a backup, we need to completely reload the cache because the ordering of the backups change and
  373. //the ordering is what the scheduler uses to get the next entry.
  374. //$row = reload_cached_rec( $buid );
  375. clear_cached_data();
  376. ensure_cached_table();
  377.  
  378. debug_println("(backupComplete) bu($buid) done", DEBUG_MED );
  379.  
  380. }
  381.  
  382.  
  383. /**
  384. * Carries out the task requested from any RPC client.
  385. * That client may be from the webserver or from a forked child or any other process.
  386. */
  387. function handle_rpc_req( $obj ){
  388. // The idea will be that the indicator before the colon will be handled generically in cases
  389. // where it is a simple function call, i.e. 'f:' (provided it is a trusted source and/or has been previously checked)
  390. global $g_data_dir;
  391. global $g_shutdown;
  392. global $debug_level;
  393. if( 'f:getbu_list_state' !== $obj[0] ){
  394. //print_r( $obj );
  395. }
  396. switch( $obj[0] ){
  397. case 'f:bu-test-file-lines':
  398. $buid = $obj[1];
  399. $lineNum = $obj[2];
  400. $lineData = '';
  401. $objFile = getObject_outputFile( $g_data_dir, $buid );
  402. $cnt = getLinesFrom( $objFile, $lineNum, $lineData );
  403. if($cnt==0){
  404. closeFile( $buid );
  405. return array( 'r:bu-test-file-lines', 0 );
  406. }
  407. return array( 'r:bu-test-file-lines', $cnt, $lineData );
  408. case 'm:fork-complete':
  409. //send the same message back to the caller
  410. return $obj;
  411. case 'f:getbu_list_all':
  412. //($table, $colIdxs) = rpc( 'f:getbu_list_all', "order by BUID" );
  413. $table = array();
  414. $colIdxs = array();
  415. getbu_list_all( $table, $colIdxs, $obj[1] );
  416. return array( $table, $colIdxs );
  417. case 'f:getbu_list_state':
  418. global $g_bu_states;
  419. if($g_bu_states==null){
  420. //print("getbu_list_state (refresh\n");
  421. $g_bu_states = getbu_list_state();
  422. }
  423. return $g_bu_states;
  424. case 'f:save_bu_schedule':
  425. $buid = $obj[1]['id'];
  426. $err = save_bu_schedule( $obj[1] );
  427. clear_cached_data();
  428. ensure_cached_table();
  429. return array($err);
  430. case 'f:save_bu_item':
  431. $buid = $obj[2];
  432. //[$err] = rpc( 'f:save_bu_item', $postVars, $bu_id );
  433. $err = save_bu_item( $obj[1], $buid );
  434. reload_cached_rec( $buid );
  435. return array($err);
  436. case 'f:set_bu_error':
  437. $buid = $obj[1];
  438. $err = set_bu_error_db( $obj[1], $obj[2] );
  439. reload_cached_rec( $buid );
  440. return array($err);
  441. case 'f:set_bu_running':
  442. $buid = $obj[1];
  443. $err = set_bu_running_db( $obj[1], $obj[2] );
  444. reload_cached_rec( $buid );
  445. return array($err);
  446. case 'f:kickoff-backup':
  447. $buid = $obj[1];
  448. $isTest = $obj[2];
  449. $res = queue_backup( $buid, false, $isTest );
  450. if($res[1]==='queued') return array('ok:','running'); //ToDo: return queued and client to handle
  451. return $res;
  452.  
  453. case 'f:set_last_run_date':
  454. //[$err] = rpc( 'f:set_last_run_date', $buid, $str_dt );
  455. $buid = $obj[1];
  456. $err = set_last_run_date_db( $buid, $obj[2] );
  457. reload_cached_rec( $buid );
  458. return array($err);
  459.  
  460. case 'f:cli-reload':
  461. clear_cached_data();
  462. ensure_cached_table();
  463. return array('ok:reload');
  464.  
  465. case 'f:cache-show':
  466. array_shift($obj);
  467. foreach( format_cache($obj) as $line){
  468. log_print( "$line\n" );
  469. }
  470. return array('ok:cache-show');
  471.  
  472. case 'f:cli-stop':
  473. $g_shutdown = true;
  474. return array('ok:shutdown');
  475.  
  476. case 'f:delete_bu_item':
  477. $buid = $obj[1];
  478. $err = delete_bu_item_db( $buid );
  479. delete_cached_rec( $buid );
  480. return array($err);
  481.  
  482. case 'f:debug_inc': //increment the debug level
  483. $debug_level++;
  484. debug_println( "debug level $debug_level" );
  485. return array('ok:');
  486. case 'f:debug_dec': //decrement the debug level
  487. $debug_level--;
  488. if($debug_level<0) $debug_level=0;
  489. debug_println( "debug level $debug_level" );
  490. return array('ok:');
  491.  
  492. }
  493. array_unshift( $obj, "Unknown Command" );
  494. print_r( $obj );
  495. return $obj;
  496. }
  497.  
  498. ?>