Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php
index af6f7f7f43..b25905668b 100755
--- a/scripts/ssh/ssh-auth.php
+++ b/scripts/ssh/ssh-auth.php
@@ -1,77 +1,87 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
-$keys = id(new PhabricatorAuthSSHKeyQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withIsActive(true)
- ->execute();
-
-if (!$keys) {
- echo pht('No keys found.')."\n";
- exit(1);
-}
+$cache = PhabricatorCaches::getMutableCache();
+$authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY;
+$authfile = $cache->getKey($authfile_key);
+
+if ($authfile === null) {
+ $keys = id(new PhabricatorAuthSSHKeyQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIsActive(true)
+ ->execute();
+
+ if (!$keys) {
+ echo pht('No keys found.')."\n";
+ exit(1);
+ }
-$bin = $root.'/bin/ssh-exec';
-foreach ($keys as $ssh_key) {
- $key_argv = array();
- $object = $ssh_key->getObject();
- if ($object instanceof PhabricatorUser) {
- $key_argv[] = '--phabricator-ssh-user';
- $key_argv[] = $object->getUsername();
- } else if ($object instanceof AlmanacDevice) {
- if (!$ssh_key->getIsTrusted()) {
- // If this key is not a trusted device key, don't allow SSH
- // authentication.
+ $bin = $root.'/bin/ssh-exec';
+ foreach ($keys as $ssh_key) {
+ $key_argv = array();
+ $object = $ssh_key->getObject();
+ if ($object instanceof PhabricatorUser) {
+ $key_argv[] = '--phabricator-ssh-user';
+ $key_argv[] = $object->getUsername();
+ } else if ($object instanceof AlmanacDevice) {
+ if (!$ssh_key->getIsTrusted()) {
+ // If this key is not a trusted device key, don't allow SSH
+ // authentication.
+ continue;
+ }
+ $key_argv[] = '--phabricator-ssh-device';
+ $key_argv[] = $object->getName();
+ } else {
+ // We don't know what sort of key this is; don't permit SSH auth.
continue;
}
- $key_argv[] = '--phabricator-ssh-device';
- $key_argv[] = $object->getName();
- } else {
- // We don't know what sort of key this is; don't permit SSH auth.
- continue;
- }
- $key_argv[] = '--phabricator-ssh-key';
- $key_argv[] = $ssh_key->getID();
+ $key_argv[] = '--phabricator-ssh-key';
+ $key_argv[] = $ssh_key->getID();
- $cmd = csprintf('%s %Ls', $bin, $key_argv);
+ $cmd = csprintf('%s %Ls', $bin, $key_argv);
- $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
- if (strlen($instance)) {
- $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd);
- }
+ $instance = PhabricatorEnv::getEnvConfig('cluster.instance');
+ if (strlen($instance)) {
+ $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd);
+ }
- // This is additional escaping for the SSH 'command="..."' string.
- $cmd = addcslashes($cmd, '"\\');
+ // This is additional escaping for the SSH 'command="..."' string.
+ $cmd = addcslashes($cmd, '"\\');
- // Strip out newlines and other nonsense from the key type and key body.
+ // Strip out newlines and other nonsense from the key type and key body.
- $type = $ssh_key->getKeyType();
- $type = preg_replace('@[\x00-\x20]+@', '', $type);
- if (!strlen($type)) {
- continue;
- }
+ $type = $ssh_key->getKeyType();
+ $type = preg_replace('@[\x00-\x20]+@', '', $type);
+ if (!strlen($type)) {
+ continue;
+ }
- $key = $ssh_key->getKeyBody();
- $key = preg_replace('@[\x00-\x20]+@', '', $key);
- if (!strlen($key)) {
- continue;
- }
+ $key = $ssh_key->getKeyBody();
+ $key = preg_replace('@[\x00-\x20]+@', '', $key);
+ if (!strlen($key)) {
+ continue;
+ }
+
+ $options = array(
+ 'command="'.$cmd.'"',
+ 'no-port-forwarding',
+ 'no-X11-forwarding',
+ 'no-agent-forwarding',
+ 'no-pty',
+ );
+ $options = implode(',', $options);
- $options = array(
- 'command="'.$cmd.'"',
- 'no-port-forwarding',
- 'no-X11-forwarding',
- 'no-agent-forwarding',
- 'no-pty',
- );
- $options = implode(',', $options);
+ $lines[] = $options.' '.$type.' '.$key."\n";
+ }
- $lines[] = $options.' '.$type.' '.$key."\n";
+ $authfile = implode('', $lines);
+ $ttl = phutil_units('24 hours in seconds');
+ $cache->setKey($authfile_key, $authfile, $ttl);
}
-echo implode('', $lines);
+echo $authfile;
exit(0);
diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
index 1fc61ffb2c..4d04707598 100644
--- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
+++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php
@@ -1,244 +1,258 @@
<?php
final class PhabricatorAuthSSHKeyEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAuthApplication';
}
public function getEditorObjectsDescription() {
return pht('SSH Keys');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_NAME;
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
$types[] = PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
return $object->getName();
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
return $object->getEntireKey();
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
return !$object->getIsActive();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
return $xaction->getNewValue();
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
return (bool)$xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$value = $xaction->getNewValue();
switch ($xaction->getTransactionType()) {
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
$object->setName($value);
return;
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY:
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value);
$type = $public_key->getType();
$body = $public_key->getBody();
$comment = $public_key->getComment();
$object->setKeyType($type);
$object->setKeyBody($body);
$object->setKeyComment($comment);
return;
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
if ($value) {
$new = null;
} else {
$new = 1;
}
$object->setIsActive($new);
return;
}
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return;
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorAuthSSHKeyTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('SSH key name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
case PhabricatorAuthSSHKeyTransaction::TYPE_KEY;
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('SSH key material is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
} else {
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
try {
$public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new);
} catch (Exception $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$ex->getMessage(),
$xaction);
}
}
}
break;
case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE:
foreach ($xactions as $xaction) {
if (!$xaction->getNewValue()) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('SSH keys can not be reactivated.'),
$xaction);
}
}
break;
}
return $errors;
}
protected function didCatchDuplicateKeyException(
PhabricatorLiskDAO $object,
array $xactions,
Exception $ex) {
$errors = array();
$errors[] = new PhabricatorApplicationTransactionValidationError(
PhabricatorAuthSSHKeyTransaction::TYPE_KEY,
pht('Duplicate'),
pht(
'This public key is already associated with another user or device. '.
'Each key must unambiguously identify a single unique owner.'),
null);
throw new PhabricatorApplicationTransactionValidationException($errors);
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function getMailSubjectPrefix() {
return pht('[SSH Key]');
}
protected function getMailThreadID(PhabricatorLiskDAO $object) {
return 'ssh-key-'.$object->getPHID();
}
+ protected function applyFinalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ // After making any change to an SSH key, drop the authfile cache so it
+ // is regenerated the next time anyone authenticates.
+ $cache = PhabricatorCaches::getMutableCache();
+ $authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY;
+ $cache->deleteKey($authfile_key);
+
+ return $xactions;
+ }
+
+
protected function getMailTo(PhabricatorLiskDAO $object) {
return $object->getObject()->getSSHKeyNotifyPHIDs();
}
protected function getMailCC(PhabricatorLiskDAO $object) {
return array();
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new PhabricatorAuthSSHKeyReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
$phid = $object->getPHID();
$mail = id(new PhabricatorMetaMTAMail())
->setSubject(pht('SSH Key %d: %s', $id, $name))
->addHeader('Thread-Topic', $phid);
// The primary value of this mail is alerting users to account compromises,
// so force delivery. In particular, this mail should still be delievered
// even if "self mail" is disabled.
$mail->setForceDelivery(true);
return $mail;
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$body->addTextSection(
pht('SECURITY WARNING'),
pht(
'If you do not recognize this change, it may indicate your account '.
'has been compromised.'));
$detail_uri = $object->getURI();
$detail_uri = PhabricatorEnv::getProductionURI($detail_uri);
$body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri);
return $body;
}
}
diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
index 4592d794fa..6ba047d100 100644
--- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
+++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php
@@ -1,130 +1,132 @@
<?php
final class PhabricatorAuthSSHKeyQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
+ const AUTHFILE_CACHEKEY = 'ssh.authfile';
+
private $ids;
private $phids;
private $objectPHIDs;
private $keys;
private $isActive;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function withKeys(array $keys) {
assert_instances_of($keys, 'PhabricatorAuthSSHPublicKey');
$this->keys = $keys;
return $this;
}
public function withIsActive($active) {
$this->isActive = $active;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthSSHKey();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $keys) {
$object_phids = mpull($keys, 'getObjectPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($object_phids)
->execute();
$objects = mpull($objects, null, 'getPHID');
foreach ($keys as $key => $ssh_key) {
$object = idx($objects, $ssh_key->getObjectPHID());
// We must have an object, and that object must be a valid object for
// SSH keys.
if (!$object || !($object instanceof PhabricatorSSHPublicKeyInterface)) {
$this->didRejectResult($ssh_key);
unset($keys[$key]);
continue;
}
$ssh_key->attachObject($object);
}
return $keys;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->keys !== null) {
$sql = array();
foreach ($this->keys as $key) {
$sql[] = qsprintf(
$conn,
'(keyType = %s AND keyIndex = %s)',
$key->getType(),
$key->getHash());
}
$where[] = implode(' OR ', $sql);
}
if ($this->isActive !== null) {
if ($this->isActive) {
$where[] = qsprintf(
$conn,
'isActive = %d',
1);
} else {
$where[] = qsprintf(
$conn,
'isActive IS NULL');
}
}
return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}
diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php
index c9bd304ccf..a725238150 100644
--- a/src/applications/cache/PhabricatorCaches.php
+++ b/src/applications/cache/PhabricatorCaches.php
@@ -1,394 +1,411 @@
<?php
/**
*
* @task request Request Cache
* @task immutable Immutable Cache
* @task setup Setup Cache
* @task compress Compression
*/
final class PhabricatorCaches extends Phobject {
private static $requestCache;
public static function getNamespace() {
return PhabricatorEnv::getEnvConfig('phabricator.cache-namespace');
}
private static function newStackFromCaches(array $caches) {
$caches = self::addNamespaceToCaches($caches);
$caches = self::addProfilerToCaches($caches);
return id(new PhutilKeyValueCacheStack())
->setCaches($caches);
}
/* -( Request Cache )------------------------------------------------------ */
/**
* Get a request cache stack.
*
* This cache stack is destroyed after each logical request. In particular,
* it is destroyed periodically by the daemons, while `static` caches are
* not.
*
* @return PhutilKeyValueCacheStack Request cache stack.
*/
public static function getRequestCache() {
if (!self::$requestCache) {
self::$requestCache = new PhutilInRequestKeyValueCache();
}
return self::$requestCache;
}
/**
* Destroy the request cache.
*
* This is called at the beginning of each logical request.
*
* @return void
*/
public static function destroyRequestCache() {
self::$requestCache = null;
}
/* -( Immutable Cache )---------------------------------------------------- */
/**
* Gets an immutable cache stack.
*
* This stack trades mutability away for improved performance. Normally, it is
* APC + DB.
*
* In the general case with multiple web frontends, this stack can not be
* cleared, so it is only appropriate for use if the value of a given key is
* permanent and immutable.
*
* @return PhutilKeyValueCacheStack Best immutable stack available.
* @task immutable
*/
public static function getImmutableCache() {
static $cache;
if (!$cache) {
$caches = self::buildImmutableCaches();
$cache = self::newStackFromCaches($caches);
}
return $cache;
}
/**
* Build the immutable cache stack.
*
* @return list<PhutilKeyValueCache> List of caches.
* @task immutable
*/
private static function buildImmutableCaches() {
$caches = array();
$apc = new PhutilAPCKeyValueCache();
if ($apc->isAvailable()) {
$caches[] = $apc;
}
$caches[] = new PhabricatorKeyValueDatabaseCache();
return $caches;
}
+ public static function getMutableCache() {
+ static $cache;
+ if (!$cache) {
+ $caches = self::buildMutableCaches();
+ $cache = self::newStackFromCaches($caches);
+ }
+ return $cache;
+ }
+
+ private static function buildMutableCaches() {
+ $caches = array();
+
+ $caches[] = new PhabricatorKeyValueDatabaseCache();
+
+ return $caches;
+ }
+
/* -( Repository Graph Cache )--------------------------------------------- */
public static function getRepositoryGraphL1Cache() {
static $cache;
if (!$cache) {
$caches = self::buildRepositoryGraphL1Caches();
$cache = self::newStackFromCaches($caches);
}
return $cache;
}
private static function buildRepositoryGraphL1Caches() {
$caches = array();
$request = new PhutilInRequestKeyValueCache();
$request->setLimit(32);
$caches[] = $request;
$apc = new PhutilAPCKeyValueCache();
if ($apc->isAvailable()) {
$caches[] = $apc;
}
return $caches;
}
public static function getRepositoryGraphL2Cache() {
static $cache;
if (!$cache) {
$caches = self::buildRepositoryGraphL2Caches();
$cache = self::newStackFromCaches($caches);
}
return $cache;
}
private static function buildRepositoryGraphL2Caches() {
$caches = array();
$caches[] = new PhabricatorKeyValueDatabaseCache();
return $caches;
}
/* -( Server State Cache )------------------------------------------------- */
/**
* Highly specialized cache for storing server process state.
*
* We use this cache to track initial steps in the setup phase, before
* configuration is loaded.
*
* This cache does NOT use the cache namespace (it must be accessed before
* we build configuration), and is global across all instances on the host.
*
* @return PhutilKeyValueCacheStack Best available server state cache stack.
* @task setup
*/
public static function getServerStateCache() {
static $cache;
if (!$cache) {
$caches = self::buildSetupCaches('phabricator-server');
// NOTE: We are NOT adding a cache namespace here! This cache is shared
// across all instances on the host.
$caches = self::addProfilerToCaches($caches);
$cache = id(new PhutilKeyValueCacheStack())
->setCaches($caches);
}
return $cache;
}
/* -( Setup Cache )-------------------------------------------------------- */
/**
* Highly specialized cache for performing setup checks. We use this cache
* to determine if we need to run expensive setup checks when the page
* loads. Without it, we would need to run these checks every time.
*
* Normally, this cache is just APC. In the absence of APC, this cache
* degrades into a slow, quirky on-disk cache.
*
* NOTE: Do not use this cache for anything else! It is not a general-purpose
* cache!
*
* @return PhutilKeyValueCacheStack Most qualified available cache stack.
* @task setup
*/
public static function getSetupCache() {
static $cache;
if (!$cache) {
$caches = self::buildSetupCaches('phabricator-setup');
$cache = self::newStackFromCaches($caches);
}
return $cache;
}
/**
* @task setup
*/
private static function buildSetupCaches($cache_name) {
// If this is the CLI, just build a setup cache.
if (php_sapi_name() == 'cli') {
return array();
}
// In most cases, we should have APC. This is an ideal cache for our
// purposes -- it's fast and empties on server restart.
$apc = new PhutilAPCKeyValueCache();
if ($apc->isAvailable()) {
return array($apc);
}
// If we don't have APC, build a poor approximation on disk. This is still
// much better than nothing; some setup steps are quite slow.
$disk_path = self::getSetupCacheDiskCachePath($cache_name);
if ($disk_path) {
$disk = new PhutilOnDiskKeyValueCache();
$disk->setCacheFile($disk_path);
$disk->setWait(0.1);
if ($disk->isAvailable()) {
return array($disk);
}
}
return array();
}
/**
* @task setup
*/
private static function getSetupCacheDiskCachePath($name) {
// The difficulty here is in choosing a path which will change on server
// restart (we MUST have this property), but as rarely as possible
// otherwise (we desire this property to give the cache the best hit rate
// we can).
// Unfortunately, we don't have a very good strategy for minimizing the
// churn rate of the cache. We previously tried to use the parent process
// PID in some cases, but this was not reliable. See T9599 for one case of
// this.
$pid_basis = getmypid();
// If possible, we also want to know when the process launched, so we can
// drop the cache if a process restarts but gets the same PID an earlier
// process had. "/proc" is not available everywhere (e.g., not on OSX), but
// check if we have it.
$epoch_basis = null;
$stat = @stat("/proc/{$pid_basis}");
if ($stat !== false) {
$epoch_basis = $stat['ctime'];
}
$tmp_dir = sys_get_temp_dir();
$tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.$name;
if (!file_exists($tmp_path)) {
@mkdir($tmp_path);
}
$is_ok = self::testTemporaryDirectory($tmp_path);
if (!$is_ok) {
$tmp_path = $tmp_dir;
$is_ok = self::testTemporaryDirectory($tmp_path);
if (!$is_ok) {
// We can't find anywhere to write the cache, so just bail.
return null;
}
}
$tmp_name = 'setup-'.$pid_basis;
if ($epoch_basis) {
$tmp_name .= '.'.$epoch_basis;
}
$tmp_name .= '.cache';
return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name;
}
/**
* @task setup
*/
private static function testTemporaryDirectory($dir) {
if (!@file_exists($dir)) {
return false;
}
if (!@is_dir($dir)) {
return false;
}
if (!@is_writable($dir)) {
return false;
}
return true;
}
private static function addProfilerToCaches(array $caches) {
foreach ($caches as $key => $cache) {
$pcache = new PhutilKeyValueCacheProfiler($cache);
$pcache->setProfiler(PhutilServiceProfiler::getInstance());
$caches[$key] = $pcache;
}
return $caches;
}
private static function addNamespaceToCaches(array $caches) {
$namespace = self::getNamespace();
if (!$namespace) {
return $caches;
}
foreach ($caches as $key => $cache) {
$ncache = new PhutilKeyValueCacheNamespace($cache);
$ncache->setNamespace($namespace);
$caches[$key] = $ncache;
}
return $caches;
}
/**
* Deflate a value, if deflation is available and has an impact.
*
* If the value is larger than 1KB, we have `gzdeflate()`, we successfully
* can deflate it, and it benefits from deflation, we deflate it. Otherwise
* we leave it as-is.
*
* Data can later be inflated with @{method:inflateData}.
*
* @param string String to attempt to deflate.
* @return string|null Deflated string, or null if it was not deflated.
* @task compress
*/
public static function maybeDeflateData($value) {
$len = strlen($value);
if ($len <= 1024) {
return null;
}
if (!function_exists('gzdeflate')) {
return null;
}
$deflated = gzdeflate($value);
if ($deflated === false) {
return null;
}
$deflated_len = strlen($deflated);
if ($deflated_len >= ($len / 2)) {
return null;
}
return $deflated;
}
/**
* Inflate data previously deflated by @{method:maybeDeflateData}.
*
* @param string Deflated data, from @{method:maybeDeflateData}.
* @return string Original, uncompressed data.
* @task compress
*/
public static function inflateData($value) {
if (!function_exists('gzinflate')) {
throw new Exception(
pht(
'%s is not available; unable to read deflated data!',
'gzinflate()'));
}
$value = gzinflate($value);
if ($value === false) {
throw new Exception(pht('Failed to inflate data!'));
}
return $value;
}
}
diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
index 99060478c0..c6a52024fe 100644
--- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
+++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
@@ -1,174 +1,174 @@
<?php
final class PhabricatorKeyValueDatabaseCache
extends PhutilKeyValueCache {
const CACHE_FORMAT_RAW = 'raw';
const CACHE_FORMAT_DEFLATE = 'deflate';
public function setKeys(array $keys, $ttl = null) {
if (PhabricatorEnv::isReadOnly()) {
return;
}
if ($keys) {
$map = $this->digestKeys(array_keys($keys));
$conn_w = $this->establishConnection('w');
$sql = array();
foreach ($map as $key => $hash) {
$value = $keys[$key];
list($format, $storage_value) = $this->willWriteValue($key, $value);
$sql[] = qsprintf(
$conn_w,
'(%s, %s, %s, %B, %d, %nd)',
$hash,
$key,
$format,
$storage_value,
time(),
$ttl ? (time() + $ttl) : null);
}
$guard = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T
(cacheKeyHash, cacheKey, cacheFormat, cacheData,
cacheCreated, cacheExpires) VALUES %Q
ON DUPLICATE KEY UPDATE
cacheKey = VALUES(cacheKey),
cacheFormat = VALUES(cacheFormat),
cacheData = VALUES(cacheData),
cacheCreated = VALUES(cacheCreated),
cacheExpires = VALUES(cacheExpires)',
$this->getTableName(),
$chunk);
}
unset($guard);
}
return $this;
}
public function getKeys(array $keys) {
$results = array();
if ($keys) {
$map = $this->digestKeys($keys);
$rows = queryfx_all(
$this->establishConnection('r'),
'SELECT * FROM %T WHERE cacheKeyHash IN (%Ls)',
$this->getTableName(),
$map);
$rows = ipull($rows, null, 'cacheKey');
foreach ($keys as $key) {
if (empty($rows[$key])) {
continue;
}
$row = $rows[$key];
if ($row['cacheExpires'] && ($row['cacheExpires'] < time())) {
continue;
}
try {
$results[$key] = $this->didReadValue(
$row['cacheFormat'],
$row['cacheData']);
} catch (Exception $ex) {
// Treat this as a cache miss.
phlog($ex);
}
}
}
return $results;
}
public function deleteKeys(array $keys) {
if ($keys) {
$map = $this->digestKeys($keys);
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)',
$this->getTableName(),
- $keys);
+ $map);
}
return $this;
}
public function destroyCache() {
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T',
$this->getTableName());
return $this;
}
/* -( Raw Cache Access )--------------------------------------------------- */
public function establishConnection($mode) {
// TODO: This is the only concrete table we have on the database right
// now.
return id(new PhabricatorMarkupCache())->establishConnection($mode);
}
public function getTableName() {
return 'cache_general';
}
/* -( Implementation )----------------------------------------------------- */
private function digestKeys(array $keys) {
$map = array();
foreach ($keys as $key) {
$map[$key] = PhabricatorHash::digestForIndex($key);
}
return $map;
}
private function willWriteValue($key, $value) {
if (!is_string($value)) {
throw new Exception(pht('Only strings may be written to the DB cache!'));
}
static $can_deflate;
if ($can_deflate === null) {
$can_deflate = function_exists('gzdeflate') &&
PhabricatorEnv::getEnvConfig('cache.enable-deflate');
}
if ($can_deflate) {
$deflated = PhabricatorCaches::maybeDeflateData($value);
if ($deflated !== null) {
return array(self::CACHE_FORMAT_DEFLATE, $deflated);
}
}
return array(self::CACHE_FORMAT_RAW, $value);
}
private function didReadValue($format, $value) {
switch ($format) {
case self::CACHE_FORMAT_RAW:
return $value;
case self::CACHE_FORMAT_DEFLATE:
return PhabricatorCaches::inflateData($value);
default:
throw new Exception(pht('Unknown cache format.'));
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 9:26 AM (2 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
321825
Default Alt Text
(31 KB)

Event Timeline