[BUGFIX] Fix 1-2-3(-4) wizard
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_lock.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2011 Michael Stucki (michael@typo3.org)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Class for providing locking features in TYPO3
29 *
30 * @author Michael Stucki <michael@typo3.org>
31 */
32
33 /**
34 * TYPO3 locking class
35 * This class provides an abstract layer to various locking features for TYPO3
36 *
37 * It is intended to blocks requests until some data has been generated.
38 * This is especially useful if two clients are requesting the same website short after each other. While the request of client 1 triggers building and caching of the website, client 2 will be waiting at this lock.
39 *
40 * @author Michael Stucki <michael@typo3.org>
41 * @package TYPO3
42 * @subpackage t3lib
43 * @see class.t3lib_tstemplate.php, class.tslib_fe.php
44 */
45 class t3lib_lock {
46
47 /**
48 * @var string Locking method: One of 'simple', 'flock', 'semaphore' or 'disable'
49 */
50 protected $method;
51
52 /**
53 * @var mixed Identifier used for this lock
54 */
55 protected $id;
56
57 /**
58 * @var mixed Resource used for this lock (can be a file or a semaphore resource)
59 */
60 protected $resource;
61
62 /**
63 * @var resource File pointer if using flock method
64 */
65 protected $filepointer;
66
67 /**
68 * @var boolean True if lock is acquired
69 */
70 protected $isAcquired = FALSE;
71
72 /**
73 * @var integer Number of times a locked resource is tried to be acquired. Only used in manual locks method "simple".
74 */
75 protected $loops = 150;
76
77 /**
78 * @var integer Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used in manual lock method "simple".
79 */
80 protected $step = 200;
81
82 /**
83 * @var string Logging facility
84 */
85 protected $syslogFacility = 'cms';
86
87 /**
88 * @var boolean True if locking should be logged
89 */
90 protected $isLoggingEnabled = TRUE;
91
92 /**
93 * Constructor:
94 * initializes locking, check input parameters and set variables accordingly.
95 *
96 * @param string $id ID to identify this lock in the system
97 * @param string $method Define which locking method to use. Defaults to "simple".
98 * @param integer $loops Number of times a locked resource is tried to be acquired. Only used in manual locks method "simple".
99 * @param integer step Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used in manual lock method "simple".
100 */
101 public function __construct($id, $method = 'simple', $loops = 0, $step = 0) {
102 // Force ID to be string
103 $id = (string) $id;
104
105 if (intval($loops)) {
106 $this->loops = intval($loops);
107 }
108 if (intval($step)) {
109 $this->step = intval($step);
110 }
111
112 $this->method = $method;
113
114 switch ($this->method) {
115 case 'simple':
116 case 'flock':
117 $path = PATH_site . 'typo3temp/locks/';
118 if (!is_dir($path)) {
119 t3lib_div::mkdir($path);
120 }
121 $this->id = md5($id);
122 $this->resource = $path . $this->id;
123 break;
124 case 'semaphore':
125 $this->id = abs(crc32($id));
126 if (($this->resource = sem_get($this->id, 1)) === FALSE) {
127 throw new RuntimeException(
128 'Unable to get semaphore',
129 1313828196
130 );
131 }
132 break;
133 case 'disable':
134 break;
135 default:
136 throw new InvalidArgumentException(
137 'No such method "' . $method . '"',
138 1294586097
139 );
140 }
141 }
142
143 /**
144 * Destructor:
145 * Releases lock automatically when instance is destroyed.
146 *
147 * @return void
148 */
149 function __destruct() {
150 $this->release();
151 }
152
153 /**
154 * Acquire a lock and return when successful. If the lock is already open, the client will be
155 *
156 * It is important to know that the lock will be acquired in any case, even if the request was blocked first. Therefore, the lock needs to be released in every situation.
157 *
158 * @return boolean Returns TRUE if lock could be acquired without waiting, FALSE otherwise.
159 */
160 public function acquire() {
161 // Default is TRUE, which means continue without caring for other clients. In the case of TYPO3s cache management, this has no negative effect except some resource overhead.
162 $noWait = TRUE;
163 $isAcquired = TRUE;
164
165 switch ($this->method) {
166 case 'simple':
167 if (is_file($this->resource)) {
168 $this->sysLog('Waiting for a different process to release the lock');
169 $maxExecutionTime = ini_get('max_execution_time');
170 $maxAge = time() - ($maxExecutionTime ? $maxExecutionTime : 120);
171 if (@filectime($this->resource) < $maxAge) {
172 @unlink($this->resource);
173 $this->sysLog('Unlink stale lockfile');
174 }
175 }
176
177 $isAcquired = FALSE;
178 for ($i = 0; $i < $this->loops; $i++) {
179 $filepointer = @fopen($this->resource, 'x');
180 if ($filepointer !== FALSE) {
181 fclose($filepointer);
182 $this->sysLog('Lock aquired');
183 $noWait = ($i === 0);
184 $isAcquired = TRUE;
185 break;
186 }
187 usleep($this->step * 1000);
188 }
189
190 if (!$isAcquired) {
191 throw new RuntimeException('Lock file could not be created', 1294586098);
192 }
193
194 t3lib_div::fixPermissions($this->resource);
195 break;
196 case 'flock':
197 if (($this->filepointer = fopen($this->resource, 'w+')) == FALSE) {
198 throw new RuntimeException('Lock file could not be opened', 1294586099);
199 }
200 // Lock without blocking
201 if (flock($this->filepointer, LOCK_EX | LOCK_NB) == TRUE) {
202 $noWait = TRUE;
203 } elseif (flock($this->filepointer, LOCK_EX) == TRUE) { // Lock with blocking (waiting for similar locks to become released)
204 $noWait = FALSE;
205 } else {
206 throw new RuntimeException('Could not lock file "' . $this->resource . '"', 1294586100);
207 }
208 break;
209 case 'semaphore':
210 if (sem_acquire($this->resource)) {
211 // Unfortunately it seems not possible to find out if the request was blocked, so we return FALSE in any case to make sure the operation is tried again.
212 $noWait = FALSE;
213 }
214 break;
215 case 'disable':
216 $noWait = FALSE;
217 $isAcquired = FALSE;
218 break;
219 }
220
221 $this->isAcquired = $isAcquired;
222 return $noWait;
223 }
224
225 /**
226 * Release the lock
227 *
228 * @return boolean Returns TRUE on success or FALSE on failure
229 */
230 public function release() {
231 if (!$this->isAcquired) {
232 return TRUE;
233 }
234
235 $success = TRUE;
236 switch ($this->method) {
237 case 'simple':
238 if (t3lib_div::isAllowedAbsPath($this->resource) && t3lib_div::isFirstPartOfStr($this->resource, PATH_site . 'typo3temp/locks/')) {
239 if (@unlink($this->resource) == FALSE) {
240 $success = FALSE;
241 }
242 }
243 break;
244 case 'flock':
245 if (flock($this->filepointer, LOCK_UN) == FALSE) {
246 $success = FALSE;
247 }
248 fclose($this->filepointer);
249 if (t3lib_div::isAllowedAbsPath($this->resource) && t3lib_div::isFirstPartOfStr($this->resource, PATH_site . 'typo3temp/locks/')) {
250 @unlink($this->resource);
251 }
252 break;
253 case 'semaphore':
254 if (@sem_release($this->resource)) {
255 sem_remove($this->resource);
256 } else {
257 $success = FALSE;
258 }
259 break;
260 case 'disable':
261 $success = FALSE;
262 break;
263 }
264
265 $this->isAcquired = FALSE;
266 return $success;
267 }
268
269 /**
270 * Return the locking method which is currently used
271 *
272 * @return string Locking method
273 */
274 public function getMethod() {
275 return $this->method;
276 }
277
278 /**
279 * Return the ID which is currently used
280 *
281 * @return string Locking ID
282 */
283 public function getId() {
284 return $this->id;
285 }
286
287 /**
288 * Return the resource which is currently used.
289 * Depending on the locking method this can be a filename or a semaphore resource.
290 *
291 * @return mixed Locking resource (filename as string or semaphore as resource)
292 */
293 public function getResource() {
294 return $this->resource;
295 }
296
297 /**
298 * Return the status of a lock
299 *
300 * @return string Returns TRUE if lock is acquired, FALSE otherwise
301 */
302 public function getLockStatus() {
303 return $this->isAcquired;
304 }
305
306 /**
307 * Sets the facility (extension name) for the syslog entry.
308 *
309 * @param string $syslogFacility
310 */
311 public function setSyslogFacility($syslogFacility) {
312 $this->syslogFacility = $syslogFacility;
313 }
314
315 /**
316 * Enable/ disable logging
317 *
318 * @param boolean $isLoggingEnabled
319 */
320 public function setEnableLogging($isLoggingEnabled) {
321 $this->isLoggingEnabled = $isLoggingEnabled;
322 }
323
324 /**
325 * Adds a common log entry for this locking API using t3lib_div::sysLog().
326 * Example: 25-02-08 17:58 - cms: Locking [simple::0aeafd2a67a6bb8b9543fb9ea25ecbe2]: Acquired
327 *
328 * @param string $message The message to be logged
329 * @param integer $severity Severity - 0 is info (default), 1 is notice, 2 is warning, 3 is error, 4 is fatal error
330 * @return void
331 */
332 public function sysLog($message, $severity = 0) {
333 if ($this->isLoggingEnabled) {
334 t3lib_div::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), $this->syslogFacility, $severity);
335 }
336 }
337 }
338
339 ?>