Files
iTop/core/apc-emulation.php

344 lines
8.1 KiB
PHP

<?php
// Copyright (c) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 iTop. If not, see <http://www.gnu.org/licenses/>
//
/**
* Date: 27/09/2017
*/
/**
* @param string $cache_type
* @param bool $limited
* @return array|bool
*/
function apc_cache_info($cache_type = '', $limited = false)
{
$aInfo = [];
$sRootCacheDir = apcFile::GetCacheFileName();
$aInfo['cache_list'] = apcFile::GetCacheEntries($sRootCacheDir);
return $aInfo;
}
/**
* @param array|string $key
* @param $var
* @param int $ttl
* @return array|bool
*/
function apc_store($key, $var = null, $ttl = 0)
{
if (is_array($key)) {
$aResult = [];
foreach ($key as $sKey => $value) {
$aResult[] = apcFile::StoreOneFile($sKey, $value, $ttl);
}
return $aResult;
}
return apcFile::StoreOneFile($key, $var, $ttl);
}
/**
* @param $key string|array
* @return mixed
*/
function apc_fetch($key)
{
if (is_array($key)) {
$aResult = [];
foreach ($key as $sKey) {
$aResult[$sKey] = apcFile::FetchOneFile($sKey);
}
return $aResult;
} elseif (is_null($key)) {
return false;
}
return apcFile::FetchOneFile($key);
}
/**
* @param string $cache_type
* @return bool
*/
function apc_clear_cache($cache_type = '')
{
apcFile::DeleteEntry(utils::GetCachePath());
return true;
}
/**
* @param $key
* @return bool|string[]
*/
function apc_delete($key)
{
if (empty($key)) {
return false;
}
$bRet1 = apcFile::DeleteEntry(apcFile::GetCacheFileName($key));
$bRet2 = apcFile::DeleteEntry(apcFile::GetCacheFileName('-'.$key));
return $bRet1 || $bRet2;
}
/**
* Checks if APCu emulation key exists
*
* @param string|string[] $keys A string, or an array of strings, that contain keys.
*
* @return bool|string[] Returns TRUE if the key exists, otherwise FALSE
* Or if an array was passed to keys, then an array is returned that
* contains all existing keys, or an empty array if none exist.
* @since 3.2.0 N°7068
*/
function apc_exists($keys)
{
if (is_array($keys)) {
$aExistingKeys = [];
foreach ($keys as $sKey) {
if (apcFile::ExistsOneFile($sKey)) {
$aExistingKeys[] = $sKey;
}
}
return $aExistingKeys;
} else {
return apcFile::ExistsOneFile($keys);
}
}
class apcFile
{
// Check only once per request
public static $aFilesByTime = null;
public static $iFileCount = 0;
/** Get the file name corresponding to the cache entry.
* If an empty key is provided, the root of the cache is returned.
* @param $sKey
* @return string
*/
public static function GetCacheFileName($sKey = '')
{
$sPath = str_replace([' ', '/', '\\', '.'], '-', $sKey ?? '');
return utils::GetCachePath().'apc-emul/'.$sPath;
}
/** Get the list of entries from a starting folder.
* @param $sEntry string starting folder.
* @return array list of entries stored into array of key 'info'
*/
public static function GetCacheEntries($sEntry)
{
$aResult = [];
if (is_dir($sEntry)) {
$aFiles = array_diff(scandir($sEntry), ['.', '..']);
foreach ($aFiles as $sFile) {
$sSubFile = $sEntry.'/'.$sFile;
$aResult = array_merge($aResult, self::GetCacheEntries($sSubFile));
}
} else {
$sKey = basename($sEntry);
if (strpos($sKey, '-') === 0) {
$sKey = substr($sKey, 1);
}
$aResult[] = ['info' => $sKey];
}
return $aResult;
}
/** Delete one cache entry.
* @param $sCache
* @return bool true if the entry was deleted false if error occurs (like entry did not exist).
*/
public static function DeleteEntry($sCache)
{
if (is_dir($sCache)) {
$aFiles = array_diff(scandir($sCache), ['.', '..']);
foreach ($aFiles as $sFile) {
$sSubFile = $sCache.'/'.$sFile;
if (!self::DeleteEntry($sSubFile)) {
return false;
}
}
if (!@rmdir($sCache)) {
return false;
}
} else {
if (is_file($sCache)) {
if (!@unlink($sCache)) {
return false;
}
} else {
return false;
}
}
self::ResetFileCount();
return true;
}
/**
* Check if cache key exists
* @param $sKey
* @return bool
* @since 3.2.0 N°7068
*/
public static function ExistsOneFile($sKey)
{
return is_file(self::GetCacheFileName('-'.$sKey)) || is_file(self::GetCacheFileName($sKey));
}
/** Get one cache entry content.
* @param $sKey
* @return bool|mixed
*/
public static function FetchOneFile($sKey)
{
// Try the 'TTLed' version
$sValue = self::ReadCacheLocked(self::GetCacheFileName('-'.$sKey));
if ($sValue === false) {
$sValue = self::ReadCacheLocked(self::GetCacheFileName($sKey));
if ($sValue === false) {
return false;
}
}
$oRes = @unserialize($sValue);
return $oRes;
}
/** Add one cache entry.
* @param string $sKey
* @param $value
* @param int $iTTL time to live
* @return bool
*/
public static function StoreOneFile($sKey, $value, $iTTL)
{
if (empty($sKey)) {
return false;
}
if (is_file(self::GetCacheFileName($sKey))) {
@unlink(self::GetCacheFileName($sKey));
}
if (is_file(self::GetCacheFileName('-'.$sKey))) {
@unlink(self::GetCacheFileName('-'.$sKey));
}
if ($iTTL > 0) {
// hint for ttl management
$sKey = '-'.$sKey;
}
$sFilename = self::GetCacheFileName($sKey);
// try to create the folder
$sDirname = dirname($sFilename);
if (!is_dir($sDirname)) {
if (!@mkdir($sDirname, 0755, true)) {
return false;
}
}
$bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false);
self::AddFile($sFilename);
return $bRes;
}
/** Manage the cache files when adding a new cache entry:
* remove older files if the mamximum is reached.
* @param $sNewFilename
*/
protected static function AddFile($sNewFilename)
{
if (strpos(basename($sNewFilename), '-') !== 0) {
return;
}
$iMaxFiles = MetaModel::GetConfig()->Get('apc_cache_emulation.max_entries');
if ($iMaxFiles == 0) {
return;
}
if (!self::$aFilesByTime) {
self::ListFilesByTime();
self::$iFileCount = count(self::$aFilesByTime);
if ($iMaxFiles !== 0) {
asort(self::$aFilesByTime);
}
} else {
self::$aFilesByTime[$sNewFilename] = time();
self::$iFileCount++;
}
if (self::$iFileCount > $iMaxFiles) {
$iFileNbToRemove = self::$iFileCount - $iMaxFiles;
foreach (self::$aFilesByTime as $sFileToRemove => $iTime) {
@unlink($sFileToRemove);
if (--$iFileNbToRemove === 0) {
break;
}
}
self::$aFilesByTime = array_slice(self::$aFilesByTime, self::$iFileCount - $iMaxFiles, null, true);
self::$iFileCount = $iMaxFiles;
}
}
/** Get the list of files with their associated access time
* @param string $sCheck Directory to scan
*/
protected static function ListFilesByTime($sCheck = null)
{
if (empty($sCheck)) {
$sCheck = self::GetCacheFileName();
}
// Garbage collection
$aFiles = array_diff(@scandir($sCheck), ['.', '..']);
foreach ($aFiles as $sFile) {
$sSubFile = $sCheck.'/'.$sFile;
if (is_dir($sSubFile)) {
self::ListFilesByTime($sSubFile);
} else {
if (strpos(basename($sSubFile), '-') === 0) {
self::$aFilesByTime[$sSubFile] = @fileatime($sSubFile);
}
}
}
}
/** Read the content of one cache file under lock protection
* @param $sFilename
* @return bool|string the content of the cache entry or false if error
*/
protected static function ReadCacheLocked($sFilename)
{
$sContent = false;
$file = @fopen($sFilename, 'r');
if ($file !== false) {
if (flock($file, LOCK_SH)) {
$sContent = file_get_contents($sFilename);
flock($file, LOCK_UN);
}
fclose($file);
}
return $sContent;
}
protected static function ResetFileCount()
{
self::$aFilesByTime = null;
self::$iFileCount = 0;
}
}