1<?php 2/** 3 * Copyright 2018 Google Inc. All Rights Reserved. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17namespace Google\Auth\Cache; 18 19use Psr\Cache\CacheItemInterface; 20use Psr\Cache\CacheItemPoolInterface; 21 22/** 23 * SystemV shared memory based CacheItemPool implementation. 24 * 25 * This CacheItemPool implementation can be used among multiple processes, but 26 * it doesn't provide any locking mechanism. If multiple processes write to 27 * this ItemPool, you have to avoid race condition manually in your code. 28 */ 29class SysVCacheItemPool implements CacheItemPoolInterface 30{ 31 const VAR_KEY = 1; 32 33 const DEFAULT_PROJ = 'A'; 34 35 const DEFAULT_MEMSIZE = 10000; 36 37 const DEFAULT_PERM = 0600; 38 39 /** 40 * @var int 41 */ 42 private $sysvKey; 43 44 /** 45 * @var CacheItemInterface[] 46 */ 47 private $items; 48 49 /** 50 * @var CacheItemInterface[] 51 */ 52 private $deferredItems; 53 54 /** 55 * @var array<mixed> 56 */ 57 private $options; 58 59 /** 60 * @var bool 61 */ 62 private $hasLoadedItems = false; 63 64 /** 65 * Create a SystemV shared memory based CacheItemPool. 66 * 67 * @param array<mixed> $options { 68 * [optional] Configuration options. 69 * 70 * @type int $variableKey The variable key for getting the data from the shared memory. **Defaults to** 1. 71 * @type string $proj The project identifier for ftok. This needs to be a one character string. **Defaults to** 'A'. 72 * @type int $memsize The memory size in bytes for shm_attach. **Defaults to** 10000. 73 * @type int $perm The permission for shm_attach. **Defaults to** 0600. 74 * } 75 */ 76 public function __construct($options = []) 77 { 78 if (! extension_loaded('sysvshm')) { 79 throw new \RuntimeException( 80 'sysvshm extension is required to use this ItemPool' 81 ); 82 } 83 $this->options = $options + [ 84 'variableKey' => self::VAR_KEY, 85 'proj' => self::DEFAULT_PROJ, 86 'memsize' => self::DEFAULT_MEMSIZE, 87 'perm' => self::DEFAULT_PERM 88 ]; 89 $this->items = []; 90 $this->deferredItems = []; 91 $this->sysvKey = ftok(__FILE__, $this->options['proj']); 92 } 93 94 /** 95 * @param mixed $key 96 * @return CacheItemInterface 97 */ 98 public function getItem($key): CacheItemInterface 99 { 100 $this->loadItems(); 101 return current($this->getItems([$key])); // @phpstan-ignore-line 102 } 103 104 /** 105 * @param array<mixed> $keys 106 * @return iterable<CacheItemInterface> 107 */ 108 public function getItems(array $keys = []): iterable 109 { 110 $this->loadItems(); 111 $items = []; 112 $itemClass = \PHP_VERSION_ID >= 80000 ? TypedItem::class : Item::class; 113 foreach ($keys as $key) { 114 $items[$key] = $this->hasItem($key) ? 115 clone $this->items[$key] : 116 new $itemClass($key); 117 } 118 return $items; 119 } 120 121 /** 122 * {@inheritdoc} 123 */ 124 public function hasItem($key): bool 125 { 126 $this->loadItems(); 127 return isset($this->items[$key]) && $this->items[$key]->isHit(); 128 } 129 130 /** 131 * {@inheritdoc} 132 */ 133 public function clear(): bool 134 { 135 $this->items = []; 136 $this->deferredItems = []; 137 return $this->saveCurrentItems(); 138 } 139 140 /** 141 * {@inheritdoc} 142 */ 143 public function deleteItem($key): bool 144 { 145 return $this->deleteItems([$key]); 146 } 147 148 /** 149 * {@inheritdoc} 150 */ 151 public function deleteItems(array $keys): bool 152 { 153 if (!$this->hasLoadedItems) { 154 $this->loadItems(); 155 } 156 157 foreach ($keys as $key) { 158 unset($this->items[$key]); 159 } 160 return $this->saveCurrentItems(); 161 } 162 163 /** 164 * {@inheritdoc} 165 */ 166 public function save(CacheItemInterface $item): bool 167 { 168 if (!$this->hasLoadedItems) { 169 $this->loadItems(); 170 } 171 172 $this->items[$item->getKey()] = $item; 173 return $this->saveCurrentItems(); 174 } 175 176 /** 177 * {@inheritdoc} 178 */ 179 public function saveDeferred(CacheItemInterface $item): bool 180 { 181 $this->deferredItems[$item->getKey()] = $item; 182 return true; 183 } 184 185 /** 186 * {@inheritdoc} 187 */ 188 public function commit(): bool 189 { 190 foreach ($this->deferredItems as $item) { 191 if ($this->save($item) === false) { 192 return false; 193 } 194 } 195 $this->deferredItems = []; 196 return true; 197 } 198 199 /** 200 * Save the current items. 201 * 202 * @return bool true when success, false upon failure 203 */ 204 private function saveCurrentItems() 205 { 206 $shmid = shm_attach( 207 $this->sysvKey, 208 $this->options['memsize'], 209 $this->options['perm'] 210 ); 211 if ($shmid !== false) { 212 $ret = shm_put_var( 213 $shmid, 214 $this->options['variableKey'], 215 $this->items 216 ); 217 shm_detach($shmid); 218 return $ret; 219 } 220 return false; 221 } 222 223 /** 224 * Load the items from the shared memory. 225 * 226 * @return bool true when success, false upon failure 227 */ 228 private function loadItems() 229 { 230 $shmid = shm_attach( 231 $this->sysvKey, 232 $this->options['memsize'], 233 $this->options['perm'] 234 ); 235 if ($shmid !== false) { 236 $data = @shm_get_var($shmid, $this->options['variableKey']); 237 if (!empty($data)) { 238 $this->items = $data; 239 } else { 240 $this->items = []; 241 } 242 shm_detach($shmid); 243 $this->hasLoadedItems = true; 244 return true; 245 } 246 return false; 247 } 248} 249