<?php
//apt-get update 1>/dev/null 2>>/root/logs/sys-update.log
//$argv[0] is always the name that was used to run the script.
if(count($argv)>1){
include __DIR__."/main-svc.php";
if($argv[1]==='stop'){
$rtn = run_main_command( 'f:cli-stop' );
print_r( $rtn );
exit(0);
}
if($argv[1]==='reload'){
$rtn = run_main_command( 'f:cli-reload' );
print_r( $rtn );
exit(0);
}
$rtn = run_main_command( $argv[1] );
print_r( $rtn );
exit(0);
}
$g_shutdown = false;
/*
* This is the backup process CLI application.
*
* Rather than use 2 threads, I've decided to poll. This is because the threading in PHP is no longer supported.
* Instead we should use parallel:
* https://www.php.net/manual/en/parallel.run.php
* which I don't like. Basically, it doesn't look like you can share code (by way of a reference)?!!
*
* So we are polling the RPC layer every 0.1secs. In practice, this will be plenty for the backup app.
*
*/
include __DIR__."/../lib/sysVcom.php";
function debug_print( $msg ){
file_put_contents( __DIR__."/../data/rpc_op.log", $msg, FILE_APPEND);
}
debug_print( date_format( date_create("now", new DateTimeZone("UTC")), 'Y-m-d H:i:s') . " started\n");
//create the comms file
touch('BU-commander');
//remove all queues at startup
clear_IPC( '../svc/BU-commander' );
rpc_setGlobals( 'procName', 'BU-commander' );
rpc_setGlobals( 'max_chan', 8182 );
rpc_setGlobals( 'main_pid', getmypid() );
print("rpc using ". __DIR__ . "/$g_procName with key = $g_k_caller\n" );
// problem is that the paths are not correct wrt this location, so we need to adjust them
include __DIR__."/../inc/paths.php";
$g_database_path = '../' . $g_database_path;
$g_data_dir = realpath( __DIR__."/../data" );
include "db-reads.php";
include "db-writes.php";
include "file-line-reader.php";
// get the latest data and cache it
$g_bu_states = null;
$g_bu_table = array();
$g_bu_colIdxs = array();
getbu_list_all( $g_bu_table, $g_bu_colIdxs, 'order by BU_DATE desc, BU_TIME desc' );
$g_fork_cnt=0;
while(true){
if($g_shutdown){
print("CLI request to shutdown: googbye!\n");
exit(0);
}
$bDidWork = false;
$bDidWork |= check_rpc_clients();
$bDidWork |= check_bu_sched();
if(!$bDidWork){
time_nanosleep( 0, 100000000);
}
}
/**
* Clears all cached data. This is called after any DB updates. It can be called at any time since loading is lazy.
*/
function clear_cached_data(){
global $g_bu_states;
global $g_bu_table;
$g_bu_states = null;
$g_bu_table = array();
}
/**
* Make sure that the main table data is populated
*/
function ensure_cached_table(){
global $g_bu_table;
if(count($g_bu_table)>0) return;
$colIdxs = null; //we don't ever reload these
getbu_list_all( $g_bu_table, $colIdxs, 'order by BU_DATE desc, BU_TIME desc' );
}
/**
* Return true if the backup was kicked off
*/
function check_bu_sched(){
global $g_bu_table;
global $g_bu_colIdxs;
global $g_fork_cnt;
if($g_fork_cnt>0){
//we limit to a single fork for now
return;
}
//get the first non null entry which is not running
$keys = array_keys( $g_bu_table );
$idfound = 0;
foreach ($keys as $key) {
if( $g_bu_table[$key][ $g_bu_colIdxs['BU_DATE'] ] == '' ) continue; //skip anything without a date
if( $g_bu_table[$key][ $g_bu_colIdxs['BuRunning'] ] == 1 ) continue; //skip any already running
if( $g_bu_table[$key][ $g_bu_colIdxs['BuError'] ] != null ) continue; //skip entries with problems
$idfound = $g_bu_table[$key][ $g_bu_colIdxs['BUID'] ];
}
if($idfound==0) return;
// is it time...?
$str_bu_dt = $g_bu_table[$idfound][ $g_bu_colIdxs['BU_DATE']] . ' ' . $g_bu_table[$idfound][ $g_bu_colIdxs['BU_TIME']] . ':00';
$schedTime = new DateTime( $str_bu_dt, new DateTimeZone("UTC") );
if( $schedTime > new DateTime('now', new DateTimeZone("UTC") ) ){
//...no it's not
return;
}
kickoff_backup( $idfound );
//print( "check after forked: running = ". $g_bu_table[$idfound][ $g_bu_colIdxs['BuRunning'] ] . "\n");
return true;
}
/**
* Kicks off a back up. Returns immediately. Deligates the work to a client fork.
*
* $scheduled:
* true if it is run according to the $schedule info in the db
* false if it has been manually initiated
*/
function kickoff_backup( $buid, $scheduled = true, $bu_test = false ){
ensure_cached_table();
global $g_bu_table;
global $g_bu_colIdxs;
global $g_fork_cnt;
$isRunning = $g_bu_table[$buid][ $g_bu_colIdxs['BuRunning'] ];
if( !$bu_test && $isRunning==1) return array("e:Error", "Backup already running" );
$i=0;
$bu_row[$i++] = $g_bu_table[$buid][ $g_bu_colIdxs['BUName'] ];
$bu_row[$i++] = $g_bu_table[$buid][ $g_bu_colIdxs['Dir_Src'] ];
$bu_row[$i++] = $g_bu_table[$buid][ $g_bu_colIdxs['Dir_Dest'] ];
$bu_row[$i++] = $g_bu_table[$buid][ $g_bu_colIdxs['Files_Ex'] ];
$bu_row[$i++] = $g_bu_table[$buid][ $g_bu_colIdxs['BuType'] ];
$bu_row[$i++] = $isRunning;
// we should be able to use pcntl_fork()
$pid = pcntl_fork();
if ( $pid == -1 ) {
debug_print("fork failed");
return array("e:Error", "fork failed" );
}
if ( $pid ) {
// parent process
debug_print( "parentpid = ". getmypid() . "\n" );
$g_bu_table[$buid][ $g_bu_colIdxs['BuRunning'] ] = 1;
return array("ok:", "running" );
} else {
// child process
$g_fork_cnt++;
$g_bu_table = array(); //data here is unique to the parent
$pid = getmypid();
debug_print( "childpid = $pid\n" );
debug_print( "kickoff_backup for $buid\n" );
include_once __DIR__."/backup.php";
$bu_result = '';
global $g_data_dir;
$g_data_dir = __DIR__."/../data";
$bu_result = run_backup_block( $buid, $bu_row, $bu_test );
debug_print( "backup ended for $buid, result = $bu_result\n" );
$obj = rpc( 'm:fork-complete', $pid, "backup-complete", $buid, $scheduled ); //we use our rpc mechanism to notify parent
//print_r( $obj );
//print( "\n" );
exit(0);
}
}
$msgArr = null;
/**
* Return true if a request was found and handled
*/
function check_rpc_clients(){
global $msgArr;
global $g_fork_cnt;
$caller_id = 0;
$obj_in = rpc_listen( $caller_id, $msgArr, false );
$obj_out = null;
if($caller_id==0) return false; //nothing to do
try{
$obj_out = handle_rpc_req( $obj_in );
}catch( Exception $e ){
debug_print( "Exception: " . $e->getMessage() ."\n" );
$obj_out = array( 'e:Exception', $e );
}
//send msg back
rpc_reply( $caller_id, $obj_out );
if( $obj_in[0] === 'm:fork-complete' ){
//clean up child process
debug_print("clean up fork child pid = {$obj_in[1]} \n");
$status;
pcntl_waitpid( $obj_in[1], $status );
$g_fork_cnt--;
debug_print("clean up done pid = {$obj_in[1]} \n");
//finish any requirements for specific child processes (db updates etc)
if( count($obj_in)>2 && $obj_in[2]="backup-complete"){
backupComplete( $obj_in[3], $obj_in[4] );
}
}
return true;
}
/**
* Called by the parent after a client notified that a backup completed.
*/
function backupComplete( $buid, $scheduled ){
global $g_bu_table;
global $g_bu_colIdxs;
//reload the table data - client updates the error field (might move this)
clear_cached_data();
$nothanks = null;
getbu_list_all( $g_bu_table, $nothanks, 'order by BU_DATE desc, BU_TIME desc' );
if($scheduled){
//if DB error, do nothing
$err = $g_bu_table[$buid][ $g_bu_colIdxs['BuError'] ];
if($err==''){
//update date and time according to the repeat field.
$date = date_create( $g_bu_table[$buid][ $g_bu_colIdxs['BU_DATE'] ] );
$dt_new = null;
switch( $g_bu_table[$buid][ $g_bu_colIdxs['BU_REPEAT'] ] ){
case 'M':
//monthly
date_add( $date, new DateInterval('P1M') );
$dt_new = date_format( $date, "Y-m-d");
break;
case 'W':
//weekly
date_add( $date, new DateInterval('P1W') );
$dt_new = date_format( $date, "Y-m-d");
break;
case 'D':
//daily
date_add( $date, new DateInterval('P1D') );
$dt_new = date_format( $date, "Y-m-d");
break;
}
$g_bu_table[$buid][ $g_bu_colIdxs['BU_DATE'] ] = $dt_new;
set_sched_date_db( $buid, $dt_new );
}
}//if manually kicked off - nothing to do
//TODO: remove the outputfiles
}
/**
* Carries out the task requested from any RPC client.
* That client may be from the webserver or from a forked child or any other process.
*/
function handle_rpc_req( $obj ){
// The idea will be that the indicator before the colon will be handled generically in cases
// where it is a simple function call, i.e. 'f:' (provided it is a trusted source and/or has been previously checked)
global $g_data_dir;
global $g_shutdown;
if( 'f:getbu_list_state' !== $obj[0] ){
//print_r( $obj );
}
switch( $obj[0] ){
case 'f:bu-test-file-lines':
$buid = $obj[1];
$lineNum = $obj[2];
$lineData = '';
$objFile = getObject_outputFile( $g_data_dir, $buid );
$cnt = getLinesFrom( $objFile, $lineNum, $lineData );
if($cnt==0){
closeFile( $buid );
return array( 'r:bu-test-file-lines', 0 );
}
return array( 'r:bu-test-file-lines', $cnt, $lineData );
case 'm:fork-complete':
//send the same message back to the caller
return $obj;
case 'f:getbu_list_all':
//($table, $colIdxs) = rpc( 'f:getbu_list_all', "order by BUID" );
$table = array();
$colIdxs = array();
getbu_list_all( $table, $colIdxs, $obj[1] );
return array( $table, $colIdxs );
case 'f:getbu_list_state':
global $g_bu_states;
if($g_bu_states==null){
//print("getbu_list_state (refresh\n");
$g_bu_states = getbu_list_state();
}
return $g_bu_states;
case 'f:save_bu_schedule':
$err = save_bu_schedule( $obj[1] );
clear_cached_data();
return array($err);
case 'f:save_bu_item':
//[$err] = rpc( 'f:save_bu_item', $postVars, $bu_id );
$err = save_bu_item( $obj[1], $obj[2] );
clear_cached_data();
return array($err);
case 'f:set_bu_error':
//print("re/setting backup error\n");
$err = set_bu_error_db( $obj[1], $obj[2] );
clear_cached_data();
return array($err);
case 'f:set_bu_running':
$err = set_bu_running_db( $obj[1], $obj[2] );
clear_cached_data();
return array($err);
case 'f:kickoff-backup':
$buid = $obj[1];
$isTest = $obj[2];
return kickoff_backup( $buid, false, $isTest );
case 'f:set_last_run_date':
//[$err] = rpc( 'f:set_last_run_date', $buid, $str_dt );
$err = set_last_run_date_db( $obj[1], $obj[2] );
clear_cached_data();
return array($err);
case 'f:cli-reload':
clear_cached_data();
return array('ok:reload');
case 'f:cli-stop':
$g_shutdown = true;
return array('ok:shutdown');
case 'f:delete_bu_item':
//[$err] = rpc( 'f:delete_bu_item', $buid );
$err = delete_bu_item_db( $obj[1] );
clear_cached_data();
return array($err);
}
array_unshift( $obj, "Unknown Command" );
print_r( $obj );
return $obj;
}
?>