diff options
Diffstat (limited to 'library/Icinga/Util/File.php')
-rw-r--r-- | library/Icinga/Util/File.php | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php new file mode 100644 index 0000000..dad332a --- /dev/null +++ b/library/Icinga/Util/File.php @@ -0,0 +1,195 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Util; + +use SplFileObject; +use ErrorException; +use RuntimeException; +use Icinga\Exception\NotWritableError; + +/** + * File + * + * A class to ease opening files and reading/writing to them. + */ +class File extends SplFileObject +{ + /** + * The mode used to open the file + * + * @var string + */ + protected $openMode; + + /** + * The access mode to use when creating directories + * + * @var int + */ + public static $dirMode = 1528; // 2770 + + /** + * @see SplFileObject::__construct() + */ + public function __construct($filename, $openMode = 'r', $useIncludePath = false, $context = null) + { + $this->openMode = $openMode; + if ($context === null) { + parent::__construct($filename, $openMode, $useIncludePath); + } else { + parent::__construct($filename, $openMode, $useIncludePath, $context); + } + } + + /** + * Create a file using the given access mode and return a instance of File open for writing + * + * @param string $path The path to the file + * @param int $accessMode The access mode to set + * @param bool $recursive Whether missing nested directories of the given path should be created + * + * @return File + * + * @throws RuntimeException In case the file cannot be created or the access mode cannot be set + * @throws NotWritableError In case the path's (existing) parent is not writable + */ + public static function create($path, $accessMode, $recursive = true) + { + $dirPath = dirname($path); + if ($recursive && !is_dir($dirPath)) { + static::createDirectories($dirPath); + } elseif (! is_writable($dirPath)) { + throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath)); + } + + $file = new static($path, 'x+'); + + if (! @chmod($path, $accessMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Cannot set access mode "%s" on file "%s" (%s)', + decoct($accessMode), + $path, + $error['message'] + )); + } + + return $file; + } + + /** + * Create missing directories + * + * @param string $path + * + * @throws RuntimeException In case a directory cannot be created or the access mode cannot be set + */ + protected static function createDirectories($path) + { + $part = strpos($path, DIRECTORY_SEPARATOR) === 0 ? DIRECTORY_SEPARATOR : ''; + foreach (explode(DIRECTORY_SEPARATOR, ltrim($path, DIRECTORY_SEPARATOR)) as $dir) { + $part .= $dir . DIRECTORY_SEPARATOR; + + if (! is_dir($part)) { + if (! @mkdir($part, static::$dirMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Failed to create missing directory "%s" (%s)', + $part, + $error['message'] + )); + } + + if (! @chmod($part, static::$dirMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Failed to set access mode "%s" for directory "%s" (%s)', + decoct(static::$dirMode), + $part, + $error['message'] + )); + } + } + } + } + + #[\ReturnTypeWillChange] + public function fwrite($str, $length = null) + { + $this->assertOpenForWriting(); + $this->setupErrorHandler(); + $retVal = $length === null ? parent::fwrite($str) : parent::fwrite($str, $length); + restore_error_handler(); + return $retVal; + } + + public function ftruncate($size): bool + { + $this->assertOpenForWriting(); + $this->setupErrorHandler(); + $retVal = parent::ftruncate($size); + restore_error_handler(); + return $retVal; + } + + #[\ReturnTypeWillChange] + public function ftell() + { + $this->setupErrorHandler(); + $retVal = parent::ftell(); + restore_error_handler(); + return $retVal; + } + + public function flock($operation, &$wouldblock = null): bool + { + $this->setupErrorHandler(); + $retVal = parent::flock($operation, $wouldblock); + restore_error_handler(); + return $retVal; + } + + #[\ReturnTypeWillChange] + public function fgetc() + { + $this->setupErrorHandler(); + $retVal = parent::fgetc(); + restore_error_handler(); + return $retVal; + } + + public function fflush(): bool + { + $this->setupErrorHandler(); + $retVal = parent::fflush(); + restore_error_handler(); + return $retVal; + } + + /** + * Setup an error handler that throws a RuntimeException for every emitted E_WARNING + */ + protected function setupErrorHandler() + { + set_error_handler( + function ($errno, $errstr, $errfile, $errline) { + restore_error_handler(); + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + }, + E_WARNING + ); + } + + /** + * Assert that the file was opened for writing and throw an exception otherwise + * + * @throws NotWritableError In case the file was not opened for writing + */ + protected function assertOpenForWriting() + { + if (!preg_match('@w|a|\+@', $this->openMode)) { + throw new NotWritableError('File not open for writing'); + } + } +} |