276 lines
10 KiB
PHP
276 lines
10 KiB
PHP
|
<?php
|
||
|
/*********************************************************************************
|
||
|
*
|
||
|
* TimeTrex is a Workforce Management program developed by
|
||
|
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it under
|
||
|
* the terms of the GNU Affero General Public License version 3 as published by
|
||
|
* the Free Software Foundation with the addition of the following permission
|
||
|
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
|
||
|
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
|
||
|
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||
|
* details.
|
||
|
*
|
||
|
*
|
||
|
* You should have received a copy of the GNU Affero General Public License along
|
||
|
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
|
||
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
* 02110-1301 USA.
|
||
|
*
|
||
|
*
|
||
|
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
|
||
|
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
|
||
|
*
|
||
|
*
|
||
|
* The interactive user interfaces in modified source and object code versions
|
||
|
* of this program must display Appropriate Legal Notices, as required under
|
||
|
* Section 5 of the GNU Affero General Public License version 3.
|
||
|
*
|
||
|
*
|
||
|
* In accordance with Section 7(b) of the GNU Affero General Public License
|
||
|
* version 3, these Appropriate Legal Notices must retain the display of the
|
||
|
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
|
||
|
* feasible for technical reasons, the Appropriate Legal Notices must display
|
||
|
* the words "Powered by TimeTrex".
|
||
|
*
|
||
|
********************************************************************************/
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @package Core
|
||
|
*/
|
||
|
class LockFile {
|
||
|
var $file_name = null;
|
||
|
|
||
|
var $max_lock_file_age = 86400;
|
||
|
var $use_pid = true;
|
||
|
|
||
|
/**
|
||
|
* LockFile constructor.
|
||
|
* @param $file_name
|
||
|
*/
|
||
|
function __construct( $file_name ) {
|
||
|
$this->file_name = $file_name;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return null
|
||
|
*/
|
||
|
function getFileName() {
|
||
|
return $this->file_name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $file_name
|
||
|
* @return bool
|
||
|
*/
|
||
|
function setFileName( $file_name ) {
|
||
|
if ( $file_name != '' ) {
|
||
|
$this->file_name = $file_name;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return bool|int
|
||
|
*/
|
||
|
function getCurrentPID() {
|
||
|
if ( $this->use_pid == true && function_exists( 'getmypid' ) == true ) {
|
||
|
$retval = getmypid();
|
||
|
//Debug::Text( 'Current PID: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param int|string $pid Process ID
|
||
|
* @return bool|null
|
||
|
*/
|
||
|
function isPIDRunning( $pid ) {
|
||
|
if ( $pid == '~STARTING' ) { //Used in create()
|
||
|
Debug::Text( 'PID is ~STARTING, assume its running: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return true;
|
||
|
} else if ( $this->use_pid == true && (int)$pid > 0 && function_exists( 'posix_getpgid' ) == true ) {
|
||
|
if ( posix_getpgid( $pid ) === false ) {
|
||
|
Debug::Text( ' PID: '. $pid .' is NOT running!', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return false;
|
||
|
} else {
|
||
|
Debug::Text( ' PID: '. $pid .' IS running!', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return true;
|
||
|
}
|
||
|
} else {
|
||
|
if ( trim( $pid ) == '' ) {
|
||
|
Debug::Text( 'PID is blank, assume its NOT running: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return false;
|
||
|
} else {
|
||
|
if ( OPERATING_SYSTEM == 'WIN' ) {
|
||
|
//Debug::Text( 'Checking if PID is running on Windows: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
//Sometimes Windows can return: shell_exec(): Unable to execute 'tasklist.exe /FI "PID eq 13564" /FO CSV'
|
||
|
// Not sure why, but silence the warning for now.
|
||
|
$processes = array_map( 'str_getcsv', explode( "\n", @shell_exec( 'tasklist.exe /FI "PID eq ' . $pid . '" /FO CSV' ) ) ); //Filter tasklist to return just the PID we are looking for in CSV format.
|
||
|
array_shift( $processes ); //Strip the first (header) off the array.
|
||
|
if ( is_array( $processes ) ) {
|
||
|
foreach ( $processes as $process ) {
|
||
|
if ( isset( $process[1] ) && (int)$process[1] == (int)$pid ) { //PID
|
||
|
Debug::Text( ' PID IS running: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Debug::Text( ' PID is NOT running: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return false;
|
||
|
} else {
|
||
|
Debug::Text( 'Unable to get process list...', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
} else {
|
||
|
Debug::Text( ' ERROR: Unable to determine if PID is running... PID: ' . $pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null; //Assuming the process is still running if the file exists and PID is invalid.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return bool|int
|
||
|
*/
|
||
|
function create( $initialize = false ) {
|
||
|
//Attempt to create directory if it does not already exist.
|
||
|
$file_name = $this->getFileName();
|
||
|
|
||
|
$dir = dirname( $file_name );
|
||
|
if ( file_exists( $dir ) == false ) {
|
||
|
$mkdir_result = @mkdir( $dir, 0777, true ); //ugo+rwx
|
||
|
if ( $mkdir_result == false ) {
|
||
|
Debug::Text( 'ERROR: Unable to create lock file directory: ' . $dir, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
} else {
|
||
|
Debug::Text( 'WARNING: Created lock file directory as it didnt exist: ' . $dir, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$current_pid = $this->getCurrentPID();
|
||
|
|
||
|
//Write current PID to file, so we can check if its still running later on.
|
||
|
$lock_file_pid = $this->readPIDFile( $file_name );
|
||
|
if ( ( $initialize == true && $lock_file_pid === false ) || ( $initialize == false && ( $lock_file_pid == '~STARTING' || $lock_file_pid === false ) ) ) {
|
||
|
//Write file with locking, this prevents duplicate lock files with the same name from being created.
|
||
|
$fp = @fopen( $file_name, 'wb');
|
||
|
if ( $fp ) {
|
||
|
Debug::Text( ' Creating Lock File: ' . $file_name .' Initialize: '. (int)$initialize .' Existing Lock File PID: '. $lock_file_pid .' Current PID: '. $current_pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
@flock( $fp, LOCK_EX );
|
||
|
@chmod( $file_name, 0660 ); //ug+rw
|
||
|
@fwrite( $fp, ( ( $initialize == true ) ? '~STARTING' : $current_pid ) ); // ~STARTING is used in isPIDRunning()
|
||
|
@flock( $fp, LOCK_UN );
|
||
|
@fclose( $fp );
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
Debug::Text( ' ERROR: Unable to create Lock File: ' . $file_name .' Initialize: '. (int)$initialize, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
} else {
|
||
|
Debug::Text( ' ERROR: Unable to create Lock File: ' . $file_name .' already exists with PID: '. $lock_file_pid .'... Initialize: '. (int)$initialize, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return bool
|
||
|
*/
|
||
|
function delete( $check_own_pid = true ) {
|
||
|
$current_pid = $this->getCurrentPID();
|
||
|
if ( file_exists( $this->getFileName() ) ) {
|
||
|
if ( $check_own_pid == true ) {
|
||
|
$lock_file_pid = $this->readPIDFile( $this->getFileName() );
|
||
|
if ( is_numeric( $lock_file_pid ) ) {
|
||
|
if ( $current_pid != $lock_file_pid ) {
|
||
|
Debug::Text( 'ERROR: Lock file is NOT our own, unable to delete... Lock File: ' . $this->getFileName() .' PID: '. $lock_file_pid .' Current PID: '. $current_pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
Debug::Text( 'Lock file does not exist or is starting... Lock File: ' . $this->getFileName() .' PID: '. $lock_file_pid .' Current PID: '. $current_pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Debug::Text( ' Deleting Lock File: ' . $this->getFileName() .' PID: '. $current_pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return Misc::unlink( $this->getFileName() );
|
||
|
} else {
|
||
|
Debug::text( ' WARNING: Failed to delete lock file, does not exist: ' . $this->getFileName() .' PID: '. $current_pid, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param $file_name
|
||
|
* @return false|int
|
||
|
*/
|
||
|
function readPIDFile( $file_name ) {
|
||
|
clearstatcache( true, $file_name );
|
||
|
if ( file_exists( $file_name ) ) {
|
||
|
$lock_file_pid = @file_get_contents( $file_name );
|
||
|
if ( $lock_file_pid != '' ) {
|
||
|
if ( $lock_file_pid != '~STARTING' ) {
|
||
|
$lock_file_pid = (int)$lock_file_pid;
|
||
|
}
|
||
|
Debug::text( ' Lock file exists with PID: ' . $lock_file_pid . ' Lock File: ' . $file_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $lock_file_pid;
|
||
|
} else {
|
||
|
Debug::text( ' Lock file exists (or did) but does not contain a PID: ' . $lock_file_pid . ' Lock File: ' . $file_name, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return bool|null
|
||
|
*/
|
||
|
function exists() {
|
||
|
//Ignore lock files older than max_lock_file_age, so if the server crashes or is rebooted during an operation, it will start again the next day.
|
||
|
clearstatcache();
|
||
|
|
||
|
$lock_file_pid = $this->readPIDFile( $this->getFileName() );
|
||
|
if ( $lock_file_pid !== false ) {
|
||
|
//Check to see if PID is still running or not.
|
||
|
$pid_running = $this->isPIDRunning( $lock_file_pid );
|
||
|
if ( $pid_running !== null ) {
|
||
|
//PID result is reliable, use it.
|
||
|
if ( $pid_running === false ) {
|
||
|
Debug::text( ' Stale (PID not running) lock file exists with PID: ' . $lock_file_pid .' Removing Lock File: '. $this->getFileName(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
Misc::unlink( $this->getFileName() );
|
||
|
} else if ( ( $pid_running == '~STARTING' && ( time() - @filemtime( $this->getFileName() ) ) > 300 ) ) { //If lock file is in "STARTING" state for more than 5 minutes, consider it stale.
|
||
|
Debug::text( ' Stale lock file exists in STARTING mode... PID: ' . $lock_file_pid .' Removing Lock File: '. $this->getFileName(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
Misc::unlink( $this->getFileName() );
|
||
|
}
|
||
|
|
||
|
return $pid_running;
|
||
|
} else if ( ( time() - @filemtime( $this->getFileName() ) ) > $this->max_lock_file_age ) {
|
||
|
//PID result may not be reliable, fall back to using file time instead.
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
?>
|