From 14908a4172291fbc0e5b81c1f3c1041c916af724 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 21 Jul 2016 09:51:36 +0200 Subject: [PATCH] implement PHP 5.4+ stream_metadata method for VFS, allowing to use that functionality from other stream-wrappers like php-smbclient --- api/src/Vfs.php | 49 ++++++++++++---- api/src/Vfs/Filesystem/StreamWrapper.php | 73 ++++++------------------ api/src/Vfs/Links/StreamWrapper.php | 23 ++++++++ api/src/Vfs/Sqlfs/StreamWrapper.php | 51 +++++++++++++++-- api/src/Vfs/StreamWrapper.php | 49 ++++++++++++++++ 5 files changed, 175 insertions(+), 70 deletions(-) diff --git a/api/src/Vfs.php b/api/src/Vfs.php index e944c63206..8f6a57c6a7 100644 --- a/api/src/Vfs.php +++ b/api/src/Vfs.php @@ -94,6 +94,14 @@ class Vfs * Name of the lock table */ const LOCK_TABLE = 'egw_locks'; + /** + * How much should be logged to the apache error-log + * + * 0 = Nothing + * 1 = only errors + * 2 = all function calls and errors (contains passwords too!) + */ + const LOG_LEVEL = 1; /** * Current user has root rights, no access checks performed! * @@ -603,8 +611,10 @@ class Vfs if ($options['url']) { - $lstat = @lstat($path); - $stat = array_slice($lstat,13); // remove numerical indices 0-12 + if (($stat = @lstat($path))) + { + $stat = array_slice($stat,13); // remove numerical indices 0-12 + } } else { @@ -2145,6 +2155,21 @@ class Vfs return $vfs->resolve_url_symlinks($_path, $file_exists, $resolve_last_symlink, $stat); } + /** + * Resolve the given path according to our fstab + * + * @param string $_path + * @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!) + * @param boolean $use_symlinkcache =true + * @param boolean $replace_user_pass_host =true replace $user,$pass,$host in url, default true, if false result is not cached + * @param boolean $fix_url_query =false true append relativ path to url query parameter, default not + * @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry + */ + static function resolve_url($_path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true,$fix_url_query=false) + { + Vfs\StreamWrapper::resolve_url($_path, $do_symlink, $use_symlinkcache, $replace_user_pass_host, $fix_url_query); + } + /** * This method is called in response to mkdir() calls on URL paths associated with the wrapper. * @@ -2262,7 +2287,7 @@ class Vfs } /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * touch just running on VFS path * * @param string $path * @param int $time =null modification time (unix timestamp), default null = current time @@ -2271,11 +2296,11 @@ class Vfs */ static function touch($path,$time=null,$atime=null) { - return self::_call_on_backend('touch',array($path,$time,$atime)); + return $path[0] == '/' && touch(self::PREFIX.$path, $time, $atime); } /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * chmod just running on VFS path * * Requires owner or root rights! * @@ -2285,35 +2310,35 @@ class Vfs */ static function chmod($path,$mode) { - return self::_call_on_backend('chmod',array($path,$mode)); + return $path[0] == '/' && chmod(self::PREFIX.$path, $mode); } /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * chmod just running on VFS path * * Requires root rights! * * @param string $path - * @param int $owner numeric user id + * @param int|string $owner numeric user id or account-name * @return boolean true on success, false otherwise */ static function chown($path,$owner) { - return self::_call_on_backend('chown',array($path,$owner)); + return $path[0] == '/' && chown(self::PREFIX.$path, $owner); } /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * chgrp just running on VFS path * * Requires owner or root rights! * * @param string $path - * @param int $group numeric group id + * @param int|string $group numeric group id or group-name * @return boolean true on success, false otherwise */ static function chgrp($path,$group) { - return self::_call_on_backend('chgrp',array($path,$group)); + return $path[0] == '/' && chown(self::PREFIX.$path, $group); } /** diff --git a/api/src/Vfs/Filesystem/StreamWrapper.php b/api/src/Vfs/Filesystem/StreamWrapper.php index 6646865e4a..0c22a70846 100644 --- a/api/src/Vfs/Filesystem/StreamWrapper.php +++ b/api/src/Vfs/Filesystem/StreamWrapper.php @@ -432,16 +432,27 @@ class StreamWrapper implements Vfs\StreamWrapperIface } /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * StreamWrapper method (PHP 5.4+) for touch, chmod, chown and chgrp + * + * We only implement touch, as other functionality would require webserver to run as root. * * @param string $url - * @param int $time =null modification time (unix timestamp), default null = current time - * @param int $atime =null access time (unix timestamp), default null = current time, not implemented in the vfs! - * @return boolean true on success, false otherwise + * @param int $option STREAM_META_(TOUCH|ACCESS|((OWNER|GROUP)(_NAME)?)) + * @param array|int|string $value + * - STREAM_META_TOUCH array($time, $atime) + * - STREAM_META_ACCESS int + * - STREAM_(OWNER|GROUP) int + * - STREAM_(OWNER|GROUP)_NAME string + * @return boolean true on success, false on failure */ - static function touch($url,$time=null,$atime=null) + function stream_metadata($url, $option, $value) { - $path = Vfs::decodePath(Vfs::parse_url($url,PHP_URL_PATH)); + if ($option != STREAM_META_TOUCH) + { + return false; // not implemented / supported + } + + $path = Vfs::decodePath(Vfs::parse_url($url, PHP_URL_PATH)); $parent = dirname($path); // check access rights (in real filesystem AND by mount perms) @@ -450,55 +461,9 @@ class StreamWrapper implements Vfs\StreamWrapperIface if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!"); return false; } - return touch($path,$time,$atime); - } - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Not supported, as it would require root rights! - * - * @param string $path - * @param string $mode mode string see Vfs::mode2int - * @return boolean true on success, false otherwise - */ - static function chmod($path,$mode) - { - unset($path, $mode); // not used, but required by interface - - return false; - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Not supported, as it would require root rights! - * - * @param string $path - * @param int $owner numeric user id - * @return boolean true on success, false otherwise - */ - static function chown($path,$owner) - { - unset($path, $owner); // not used, but required by interface - - return false; - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Not supported, as it would require root rights! - * - * @param string $path - * @param int $group numeric group id - * @return boolean true on success, false otherwise - */ - static function chgrp($path,$group) - { - unset($path, $group); // not used, but required by interface - - return false; + array_unshift($value, $path); + return call_user_func_array('touch', $value); } /** diff --git a/api/src/Vfs/Links/StreamWrapper.php b/api/src/Vfs/Links/StreamWrapper.php index e74124e875..e33d5595dd 100644 --- a/api/src/Vfs/Links/StreamWrapper.php +++ b/api/src/Vfs/Links/StreamWrapper.php @@ -329,6 +329,29 @@ class StreamWrapper extends LinksParent return parent::dir_opendir($url, $options); } + /** + * Reimplemented to create an entry directory on the fly AND delete our stat cache! + * + * @param string $url + * @param int $time =null modification time (unix timestamp), default null = current time + * @param int $atime =null access time (unix timestamp), default null = current time, not implemented in the vfs! + */ + protected function touch($url,$time=null,$atime=null) + { + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$time,$atime)"); + + if (!($stat = self::url_stat($url,STREAM_URL_STAT_QUIET))) + { + // file does not exist --> create an empty one + if (!($f = fopen(self::SCHEME.'://default'.Vfs::parse_url($url,PHP_URL_PATH),'w')) || !fclose($f)) + { + return false; + } + } + + return is_null($time) ? true : parent::touch($url,$time,$atime); + } + /** * Register this stream-wrapper */ diff --git a/api/src/Vfs/Sqlfs/StreamWrapper.php b/api/src/Vfs/Sqlfs/StreamWrapper.php index fae15a835b..caf77b9fd4 100644 --- a/api/src/Vfs/Sqlfs/StreamWrapper.php +++ b/api/src/Vfs/Sqlfs/StreamWrapper.php @@ -835,6 +835,49 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface return $ret; } + /** + * StreamWrapper method (PHP 5.4+) for touch, chmod, chown and chgrp + * + * We use protected helper methods touch, chmod, chown and chgrp to implement the functionality. + * + * @param string $path + * @param int $option STREAM_META_(TOUCH|ACCESS|((OWNER|GROUP)(_NAME)?)) + * @param array|int|string $value + * - STREAM_META_TOUCH array($time, $atime) + * - STREAM_META_ACCESS int + * - STREAM_(OWNER|GROUP) int + * - STREAM_(OWNER|GROUP)_NAME string + * @return boolean true on success, false on failure + */ + function stream_metadata($path, $option, $value) + { + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path, $option, ".array2string($value).")"); + + switch($option) + { + case STREAM_META_TOUCH: + return $this->touch($path, $value[0]); // atime is not supported + + case STREAM_META_ACCESS: + return $this->chmod($path, $value); + + case STREAM_META_OWNER_NAME: + if (($value = $GLOBALS['egw']->account->name2id($value, 'account_lid', 'u')) === false) + return false; + // fall through + case STREAM_META_OWNER: + return $this->chown($path, $value); + + case STREAM_META_GROUP_NAME: + if (($value = $GLOBALS['egw']->account->name2id($value, 'account_lid', 'g')) === false) + return false; + // fall through + case STREAM_META_GROUP: + return $this->chgrp($path, $value); + } + return false; + } + /** * This is not (yet) a stream-wrapper function, but it's necessary and can be used static * @@ -842,7 +885,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $time =null modification time (unix timestamp), default null = current time * @param int $atime =null access time (unix timestamp), default null = current time, not implemented in the vfs! */ - static function touch($url,$time=null,$atime=null) + protected function touch($url,$time=null,$atime=null) { unset($atime); // not used if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url, $time)"); @@ -880,7 +923,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $owner * @return boolean */ - static function chown($url,$owner) + protected function chown($url,$owner) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)"); @@ -925,7 +968,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $owner * @return boolean */ - static function chgrp($url,$owner) + protected function chgrp($url,$owner) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)"); @@ -971,7 +1014,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $mode * @return boolean */ - static function chmod($url,$mode) + protected function chmod($url,$mode) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url, $mode)"); diff --git a/api/src/Vfs/StreamWrapper.php b/api/src/Vfs/StreamWrapper.php index d8f21e5a8a..10b36ab9c9 100644 --- a/api/src/Vfs/StreamWrapper.php +++ b/api/src/Vfs/StreamWrapper.php @@ -480,6 +480,55 @@ class StreamWrapper implements StreamWrapperIface return fstat($this->opened_stream); } + /** + * StreamWrapper method (PHP 5.4+) for touch, chmod, chown and chgrp + * + * @param string $path + * @param int $option STREAM_META_(TOUCH|ACCESS|((OWNER|GROUP)(_NAME)?)) + * @param array|int|string $value + * - STREAM_META_TOUCH array($time, $atime) + * - STREAM_META_ACCESS int + * - STREAM_(OWNER|GROUP) int + * - STREAM_(OWNER|GROUP)_NAME string + * @return boolean true on success, false on failure + */ + function stream_metadata($path, $option, $value) + { + if (!($url = $this->resolve_url_symlinks($path, $option == STREAM_META_TOUCH, false))) // true,false file need to exist, but do not resolve last component + { + return false; + } + if (self::url_is_readonly($url)) + { + return false; + } + if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path', $option, ".array2string($value).") url=$url"); + + switch($option) + { + case STREAM_META_TOUCH: + return touch($url, $value[0]); // atime is not supported + + case STREAM_META_ACCESS: + return chmod($url, $value); + + case STREAM_META_OWNER_NAME: + if (($value = $GLOBALS['egw']->account->name2id($value, 'account_lid', 'u')) === false) + return false; + // fall through + case STREAM_META_OWNER: + return chown($url, $value); + + case STREAM_META_GROUP_NAME: + if (($value = $GLOBALS['egw']->account->name2id($value, 'account_lid', 'g')) === false) + return false; + // fall through + case STREAM_META_GROUP: + return chgrp($url, $value); + } + return false; + } + /** * This method is called in response to unlink() calls on URL paths associated with the wrapper. *