| @@ -150,9 +150,10 @@ | ||
| 150 | 150 | * |
| 151 | 151 | * @return bool |
| 152 | 152 | */ |
| 153 | 153 | protected function compare_hashes_on_different_os( $file_path, $file_hash ) { |
| 154 | - if ( hash_equals( md5_file( $file_path ), $file_hash ) ) { | |
| 154 | + $hashed_file = md5_file( $file_path ); | |
| 155 | + if ( false !== $hashed_file && hash_equals( $hashed_file, $file_hash ) ) { | |
| 155 | 156 | return true; |
| 156 | 157 | } |
| 157 | 158 | if ( hash_equals( $this->hash_file( $file_path, 'linux' ), $file_hash ) ) { |
| 158 | 159 | return true; |
| @@ -177,9 +178,9 @@ | ||
| 177 | 178 | return $this->compare_hashes_on_different_os( $file_path, $file_hash ); |
| 178 | 179 | } elseif ( is_array( $file_hash ) ) { |
| 179 | 180 | // Sometimes file has some hashes. |
| 180 | 181 | foreach ( $file_hash as $hash_value ) { |
| 181 | - if ( $this->compare_hashes_on_different_os( $file_path, $hash_value ) ) { | |
| 182 | + if ( is_string( $hash_value ) && $this->compare_hashes_on_different_os( $file_path, $hash_value ) ) { | |
| 182 | 183 | return true; |
| 183 | 184 | } |
| 184 | 185 | } |
| 185 | 186 | |
| @@ -225,57 +226,72 @@ | ||
| 225 | 226 | |
| 226 | 227 | /** |
| 227 | 228 | * Retrieves the lock file path used in scanning. |
| 228 | 229 | * |
| 230 | + * @param string $lock_filename The lock file name. | |
| 231 | + * | |
| 229 | 232 | * @return string The lock file path. |
| 230 | 233 | * |
| 231 | 234 | * @throws \RuntimeException If the lock file name is not defined. |
| 232 | 235 | */ |
| 233 | - protected function get_lock_path(): string { | |
| 234 | - if ( '' === $this->lock_filename ) { | |
| 236 | + protected function get_lock_path( string $lock_filename ): string { | |
| 237 | + if ( '' === $lock_filename ) { | |
| 235 | 238 | throw new \RuntimeException( 'Lock file name must be defined in the class using IO trait.' ); |
| 236 | 239 | } |
| 237 | 240 | |
| 238 | - return $this->get_tmp_path() . DIRECTORY_SEPARATOR . $this->lock_filename; | |
| 241 | + return $this->get_tmp_path() . DIRECTORY_SEPARATOR . $lock_filename; | |
| 239 | 242 | } |
| 240 | 243 | |
| 241 | 244 | /** |
| 242 | - * Create a file lock, so we can check if a process already running. | |
| 243 | - */ | |
| 244 | - public function create_lock() { | |
| 245 | - $this->remove_lock(); | |
| 246 | - file_put_contents( $this->get_lock_path(), time(), LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents | |
| 247 | - } | |
| 248 | - | |
| 249 | - /** | |
| 250 | 245 | * Delete file lock. |
| 246 | + * | |
| 247 | + * @param string $lock_filename The lock file name. | |
| 251 | 248 | */ |
| 252 | - public function remove_lock() { | |
| 253 | - if ( file_exists( $this->get_lock_path() ) ) { | |
| 254 | - wp_delete_file( $this->get_lock_path() ); | |
| 249 | + public function remove_lock( string $lock_filename ) { | |
| 250 | + $lock_path = $this->get_lock_path( $lock_filename ); | |
| 251 | + if ( file_exists( $lock_path ) ) { | |
| 252 | + wp_delete_file( $lock_path ); | |
| 255 | 253 | } |
| 256 | 254 | } |
| 257 | 255 | |
| 258 | 256 | /** |
| 259 | - * Check if a lock is valid. | |
| 257 | + * Atomically acquire a file lock. | |
| 260 | 258 | * |
| 261 | - * @return bool | |
| 259 | + * @param string $lock_filename The lock filename. | |
| 260 | + * | |
| 261 | + * @return bool True when the lock is acquired. | |
| 262 | 262 | */ |
| 263 | - public function has_lock(): bool { | |
| 263 | + public function try_create_lock( string $lock_filename ): bool { | |
| 264 | 264 | global $wp_filesystem; |
| 265 | - // Initialize the WP filesystem, no more using 'file-put-contents' function. | |
| 265 | + | |
| 266 | 266 | if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) { |
| 267 | 267 | require_once ABSPATH . '/wp-admin/includes/file.php'; |
| 268 | 268 | WP_Filesystem(); |
| 269 | 269 | } |
| 270 | - if ( ! file_exists( $this->get_lock_path() ) ) { | |
| 271 | - return false; | |
| 270 | + | |
| 271 | + $lock_path = $this->get_lock_path( $lock_filename ); | |
| 272 | + | |
| 273 | + if ( is_file( $lock_path ) ) { | |
| 274 | + $time = (int) $wp_filesystem->get_contents( $lock_path ); | |
| 275 | + | |
| 276 | + // Existing lock is still valid. | |
| 277 | + if ( ( $time + 90 ) >= time() ) { | |
| 278 | + return false; | |
| 279 | + } | |
| 280 | + | |
| 281 | + // Remove stale lock. | |
| 282 | + wp_delete_file( $lock_path ); | |
| 272 | 283 | } |
| 273 | - $time = $wp_filesystem->get_contents( $this->get_lock_path() ); | |
| 274 | - if ( strtotime( '+90 seconds', $time ) < time() ) { | |
| 275 | - // Usually a timeout window is 30 seconds, so we should allow lock at 1.30min for safe. | |
| 284 | + | |
| 285 | + $handle = @fopen( $lock_path, 'x' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.PHP.NoSilencedErrors.Discouraged | |
| 286 | + | |
| 287 | + if ( false === $handle ) { | |
| 288 | + // Another process acquired the lock. | |
| 276 | 289 | return false; |
| 277 | 290 | } |
| 291 | + | |
| 292 | + fwrite( $handle, (string) time() ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite | |
| 293 | + fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose | |
| 278 | 294 | |
| 279 | 295 | return true; |
| 280 | 296 | } |
| 281 | 297 | |