diff --git a/.travis.yml b/.travis.yml index fef46f107b..9e867c2d4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,21 +55,22 @@ before_script: "hhvm") ;; esac - - php -m - # - mysql -e 'create database egroupware' + #- php -m - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini #- composer update --prefer-source $LOWEST_DEPS - mr --trust-all --stats up + - (cd activesync; mr --trust-all --stats up) # installing phpunit in version suitable for used PHP version - composer update phpunit/phpunit - # install egroupware + # create data directory - sudo mkdir /var/lib/egroupware - sudo chown travis /var/lib/egroupware script: + # install egroupware - php doc/rpm-build/post_install.php - --source_dir `pwd` --start_db '' --autostart_db '' || true - - mysql -uroot -e 'show tables' egroupware || true + --source_dir `pwd` --start_db '' --autostart_db '' --start_webserver '' --webserver_user '' + #- mysql -uroot -e 'show tables' egroupware # Ubuntu has problems with #!/usr/bin/env php -dapc.enable=1, it stalls forever - php -dapc.enable_cli=1 doc/test-cli.php - ./doc/php_syntax_check.sh diff --git a/api/src/Link.php b/api/src/Link.php index ca2ccef071..7707b4e689 100644 --- a/api/src/Link.php +++ b/api/src/Link.php @@ -1300,7 +1300,7 @@ class Link extends Link\Storage if (!is_array($fileinfo)) { $url = Vfs\Sqlfs\StreamWrapper::id2path($fileinfo); - if (!($fileinfo = Vfs::url_stat($url,STREAM_URL_STAT_QUIET))) + if (!($fileinfo = Vfs::stat($url,STREAM_URL_STAT_QUIET))) { return false; } diff --git a/api/src/Vfs.php b/api/src/Vfs.php index 492c1d9964..5dd00750ad 100644 --- a/api/src/Vfs.php +++ b/api/src/Vfs.php @@ -67,9 +67,17 @@ use HTTP_WebDAV_Server; * Vfs::parse_url($url, $component=-1), Vfs::dirname($url) and Vfs::basename($url) work * on urls containing utf-8 characters, which get NOT urlencoded in our VFS! */ -class Vfs extends Vfs\StreamWrapper +class Vfs { const PREFIX = 'vfs://default'; + /** + * Scheme / protocol used for this stream-wrapper + */ + const SCHEME = Vfs\StreamWrapper::SCHEME; + /** + * Mime type of directories, the old vfs used 'Directory', while eg. WebDAV uses 'httpd/unix-directory' + */ + const DIR_MIME_TYPE = Vfs\StreamWrapper::DIR_MIME_TYPE; /** * Readable bit, for dirs traversable */ @@ -86,6 +94,14 @@ class Vfs extends Vfs\StreamWrapper * 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! * @@ -238,11 +254,12 @@ class Vfs extends Vfs\StreamWrapper */ static function stat($path,$try_create_home=false) { - if ($path[0] != '/') + if ($path[0] != '/' && strpos($path, self::PREFIX.'/') !== 0) { throw new Exception\AssertionFailed("File '$path' is not an absolute path!"); } - if (($stat = self::url_stat($path,0,$try_create_home))) + $vfs = new Vfs\StreamWrapper(); + if (($stat = $vfs->url_stat($path,0,$try_create_home))) { $stat = array_slice($stat,13); // remove numerical indices 0-12 } @@ -262,7 +279,8 @@ class Vfs extends Vfs\StreamWrapper { throw new Exception\AssertionFailed("File '$path' is not an absolute path!"); } - if (($stat = self::url_stat($path,STREAM_URL_STAT_LINK,$try_create_home))) + $vfs = new Vfs\StreamWrapper(); + if (($stat = $vfs->url_stat($path,STREAM_URL_STAT_LINK,$try_create_home))) { $stat = array_slice($stat,13); // remove numerical indices 0-12 } @@ -317,69 +335,7 @@ class Vfs extends Vfs\StreamWrapper */ static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false) { - if (is_null($check_url)) $check_url = strpos($url,'$') === false; - - if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup - { - $api_config = Config::read('phpgwapi'); - if (isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab'])) - { - self::$fstab = $api_config['vfs_fstab']; - } - else - { - self::$fstab = array( - '/' => 'sqlfs://$host/', - '/apps' => 'links://$host/apps', - ); - } - unset($api_config); - } - if (is_null($url) || is_null($path)) - { - if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns '.array2string(self::$fstab)); - return self::$fstab; - } - if (!self::$is_root) - { - if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!'); - return false; // only root can mount - } - if ($clear_fstab) - { - self::$fstab = array(); - } - if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url) - { - if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.'); - return true; // already mounted - } - self::load_wrapper(self::parse_url($url,PHP_URL_SCHEME)); - - if ($check_url && (!file_exists($url) || opendir($url) === false)) - { - if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') url does NOT exist!'); - return false; // url does not exist - } - self::$fstab[$path] = $url; - - uksort(self::$fstab, function($a, $b) - { - return strlen($a) - strlen($b); - }); - - if ($persitent_mount) - { - Config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); - $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; - // invalidate session cache - if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited - { - $GLOBALS['egw']->invalidate_session_cache(); - } - } - if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns true (successful new mount).'); - return true; + return Vfs\StreamWrapper::mount($url, $path, $check_url, $persitent_mount, $clear_fstab); } /** @@ -389,27 +345,18 @@ class Vfs extends Vfs\StreamWrapper */ static function umount($path) { - if (!self::$is_root) - { - if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).','.array2string($path).') permission denied, you are NOT root!'); - return false; // only root can mount - } - if (!isset(self::$fstab[$path]) && ($path = array_search($path,self::$fstab)) === false) - { - if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).') NOT mounted!'); - return false; // $path not mounted - } - unset(self::$fstab[$path]); + return Vfs\StreamWrapper::umount($path); + } - Config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); - $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; - // invalidate session cache - if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited - { - $GLOBALS['egw']->invalidate_session_cache(); - } - if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($path).') returns true (successful unmount).'); - return true; + /** + * Returns mount url of a full url returned by resolve_url + * + * @param string $fullurl full url returned by resolve_url + * @return string|NULL mount url or null if not found + */ + static function mount_url($fullurl) + { + return Vfs\StreamWrapper::mount_url($fullurl); } /** @@ -679,11 +626,14 @@ class Vfs extends Vfs\StreamWrapper if ($options['url']) { - $stat = @lstat($path); + if (($stat = @lstat($path))) + { + $stat = array_slice($stat,13); // remove numerical indices 0-12 + } } else { - $stat = self::url_stat($path,STREAM_URL_STAT_LINK); + $stat = self::stat($path, STREAM_URL_STAT_LINK); } if (!$stat) { @@ -694,7 +644,6 @@ class Vfs extends Vfs\StreamWrapper { return; // wrong type } - $stat = array_slice($stat,13); // remove numerical indices 0-12 $stat['path'] = self::parse_url($path,PHP_URL_PATH); $stat['name'] = $options['remove'] > 0 ? implode('/',array_slice(explode('/',$stat['path']),$options['remove'])) : self::basename($path); @@ -802,11 +751,12 @@ class Vfs extends Vfs\StreamWrapper { $url = self::PREFIX . $url; } + $vfs = new Vfs\StreamWrapper(); if (is_dir($url) && !is_link($url)) { - return self::rmdir($url,0); + return $vfs->rmdir($url,0); } - return self::unlink($url); + return $vfs->unlink($url); } /** @@ -847,7 +797,8 @@ class Vfs extends Vfs\StreamWrapper { self::clearstatcache($path); - $path_user_stat[$path][$user] = self::url_stat($path, 0); + $vfs = new Vfs\StreamWrapper(); + $path_user_stat[$path][$user] = $vfs->url_stat($path, 0); self::clearstatcache($path); // we need to clear the stat-cache after the call too, as the next call might be the regular user again! } @@ -889,7 +840,8 @@ class Vfs extends Vfs\StreamWrapper // query stat array, if not given if (is_null($stat)) { - $stat = self::url_stat($path,0); + if (!isset($vfs)) $vfs = new Vfs\StreamWrapper(); + $stat = $vfs->url_stat($path,0); } //error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)"); @@ -1307,6 +1259,59 @@ class Vfs extends Vfs\StreamWrapper return array_pop($parts); } + /** + * Utf-8 save version of parse_url + * + * Does caching withing request, to not have to parse urls over and over again. + * + * @param string $url + * @param int $component =-1 PHP_URL_* constants + * @return array|string|boolean on success array or string, if $component given, or false on failure + */ + static function parse_url($url, $component=-1) + { + static $component2str = array( + PHP_URL_SCHEME => 'scheme', + PHP_URL_HOST => 'host', + PHP_URL_PORT => 'port', + PHP_URL_USER => 'user', + PHP_URL_PASS => 'pass', + PHP_URL_PATH => 'path', + PHP_URL_QUERY => 'query', + PHP_URL_FRAGMENT => 'fragment', + ); + static $cache = array(); // some caching + + $result =& $cache[$url]; + + if (!isset($result)) + { + // Build arrays of values we need to decode before parsing + static $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); + static $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]"); + static $str_replace = null; + if (!isset($str_replace)) $str_replace = function_exists('mb_str_replace') ? 'mb_str_replace' : 'str_replace'; + + // Create encoded URL with special URL characters decoded so it can be parsed + // All other characters will be encoded + $encodedURL = $str_replace($entities, $replacements, urlencode($url)); + + // Parse the encoded URL + $result = $encodedParts = parse_url($encodedURL); + + // Now, decode each value of the resulting array + if ($encodedParts) + { + $result = array(); + foreach ($encodedParts as $key => $value) + { + $result[$key] = urldecode($str_replace($replacements, $entities, $value)); + } + } + } + return $component >= 0 ? $result[$component2str[$component]] : $result; + } + /** * Get the directory / parent of a given path or url(!), return false for '/'! * @@ -1346,8 +1351,11 @@ class Vfs extends Vfs\StreamWrapper */ static function has_owner_rights($path,array $stat=null) { - if (!$stat) $stat = self::url_stat($path,0); - + if (!$stat) + { + $vfs = new Vfs\StreamWrapper(); + $stat = $vfs->url_stat($path,0); + } return $stat['uid'] == self::$user || // user is the owner self::$is_root || // class runs with root rights !$stat['uid'] && $stat['gid'] && self::$is_admin; // group directory and user is an eGW admin @@ -2020,11 +2028,12 @@ class Vfs extends Vfs\StreamWrapper { if (self::is_dir($dst)) { + $vfs = new Vfs\StreamWrapper(); foreach($src as $file) { $target = self::concat($dst, self::basename($file)); - if ($file != $target && self::rename($file, $target)) + if ($file != $target && $vfs->rename($file, $target)) { $moved[] = $file; } @@ -2145,6 +2154,325 @@ class Vfs extends Vfs\StreamWrapper //error_log(__METHOD__."($file1, $file2) returning ".array2string($read1 === $read2)." (content differs)"); return $read1 === $read2; } + + /** + * Resolve the given path according to our fstab AND symlinks + * + * @param string $_path + * @param boolean $file_exists =true true if file needs to exists, false if not + * @param boolean $resolve_last_symlink =true + * @param array|boolean &$stat=null on return: stat of existing file or false for non-existing files + * @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry + */ + static function resolve_url_symlinks($_path,$file_exists=true,$resolve_last_symlink=true,&$stat=null) + { + $vfs = new Vfs\StreamWrapper(); + 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. + * + * It should attempt to create the directory specified by path. + * In order for the appropriate error message to be returned, do not define this method if your wrapper does not support creating directories. + * + * @param string $path + * @param int $mode + * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE + * @return boolean TRUE on success or FALSE on failure + */ + static function mkdir ($path, $mode=0750, $options=0) + { + return $path[0] == '/' && mkdir(self::PREFIX.$path, $mode, $options); + } + + /** + * This method is called in response to rmdir() calls on URL paths associated with the wrapper. + * + * It should attempt to remove the directory specified by path. + * In order for the appropriate error message to be returned, do not define this method if your wrapper does not support removing directories. + * + * @param string $path + * @param int $options Possible values include STREAM_REPORT_ERRORS. + * @return boolean TRUE on success or FALSE on failure. + */ + static function rmdir ( $path, $options=0 ) + { + return $path[0] == '/' && rmdir(self::PREFIX.$path, $options); + } + + /** + * This method is called in response to unlink() calls on URL paths associated with the wrapper. + * + * It should attempt to delete the item specified by path. + * In order for the appropriate error message to be returned, do not define this method if your wrapper does not support unlinking! + * + * @param string $path + * @return boolean TRUE on success or FALSE on failure + */ + static function unlink ( $path ) + { + return $path[0] == '/' && unlink(self::PREFIX.$path); + } + + /** + * Allow to call methods of the underlying stream wrapper: touch, chmod, chgrp, chown, ... + * + * We cant use a magic __call() method, as it does not work for static methods! + * + * @param string $name + * @param array $params first param has to be the path, otherwise we can not determine the correct wrapper + * @param boolean $fail_silent =false should only false be returned if function is not supported by the backend, + * or should an E_USER_WARNING error be triggered (default) + * @param int $path_param_key =0 key in params containing the path, default 0 + * @return mixed return value of backend or false if function does not exist on backend + */ + static protected function _call_on_backend($name,$params,$fail_silent=false,$path_param_key=0) + { + $pathes = $params[$path_param_key]; + + $scheme2urls = array(); + foreach(is_array($pathes) ? $pathes : array($pathes) as $path) + { + if (!($url = self::resolve_url_symlinks($path,false,false))) + { + return false; + } + $k=(string)self::parse_url($url,PHP_URL_SCHEME); + if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array(); + $scheme2urls[$k][$path] = $url; + } + $ret = array(); + foreach($scheme2urls as $scheme => $urls) + { + if ($scheme) + { + if (!class_exists($class = self::scheme2class($scheme)) || !method_exists($class,$name)) + { + if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING); + return false; + } + if (!is_array($pathes)) + { + $params[$path_param_key] = $url; + + return call_user_func_array(array($class,$name),$params); + } + $params[$path_param_key] = $urls; + if (!is_array($r = call_user_func_array(array($class,$name),$params))) + { + return $r; + } + // we need to re-translate the urls to pathes, as they can eg. contain symlinks + foreach($urls as $path => $url) + { + if (isset($r[$url]) || isset($r[$url=self::parse_url($url,PHP_URL_PATH)])) + { + $ret[$path] = $r[$url]; + } + } + } + // call the filesystem specific function (dont allow to use arrays!) + elseif(!function_exists($name) || is_array($pathes)) + { + return false; + } + else + { + $time = null; + return $name($url,$time); + } + } + return $ret; + } + + /** + * touch just running on VFS path + * + * @param string $path + * @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 + */ + static function touch($path,$time=null,$atime=null) + { + return $path[0] == '/' && touch(self::PREFIX.$path, $time, $atime); + } + + /** + * chmod just running on VFS path + * + * Requires owner or 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) + { + return $path[0] == '/' && chmod(self::PREFIX.$path, $mode); + } + + /** + * chmod just running on VFS path + * + * Requires root rights! + * + * @param string $path + * @param int|string $owner numeric user id or account-name + * @return boolean true on success, false otherwise + */ + static function chown($path,$owner) + { + return $path[0] == '/' && chown(self::PREFIX.$path, $owner); + } + + /** + * chgrp just running on VFS path + * + * Requires owner or root rights! + * + * @param string $path + * @param int|string $group numeric group id or group-name + * @return boolean true on success, false otherwise + */ + static function chgrp($path,$group) + { + return $path[0] == '/' && chown(self::PREFIX.$path, $group); + } + + /** + * Returns the target of a symbolic link + * + * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * + * @param string $path + * @return string|boolean link-data or false if no link + */ + static function readlink($path) + { + $ret = self::_call_on_backend('readlink',array($path),true); // true = fail silent, if backend does not support readlink + //error_log(__METHOD__."('$path') returning ".array2string($ret).' '.function_backtrace()); + return $ret; + } + + /** + * Creates a symbolic link + * + * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * + * @param string $target target of the link + * @param string $link path of the link to create + * @return boolean true on success, false on error + */ + static function symlink($target,$link) + { + if (($ret = self::_call_on_backend('symlink',array($target,$link),false,1))) // 1=path is in $link! + { + self::symlinkCache_remove($link); + } + return $ret; + } + + /** + * This is not (yet) a stream-wrapper function, but it's necessary and can be used static + * + * The methods use the following ways to get the mime type (in that order) + * - directories (is_dir()) --> self::DIR_MIME_TYPE + * - stream implemented by class defining the STAT_RETURN_MIME_TYPE constant --> use mime-type returned by url_stat + * - for regular filesystem use mime_content_type function if available + * - use eGW's mime-magic class + * + * @param string $path + * @param boolean $recheck =false true = do a new check, false = rely on stored mime type (if existing) + * @return string mime-type (self::DIR_MIME_TYPE for directories) + */ + static function mime_content_type($path,$recheck=false) + { + if (!($url = self::resolve_url_symlinks($path))) + { + return false; + } + if (($scheme = self::parse_url($url,PHP_URL_SCHEME)) && !$recheck) + { + // check it it's an eGW stream wrapper returning mime-type via url_stat + // we need to first check if the constant is defined, as we get a fatal error in php5.3 otherwise + if (class_exists($class = self::scheme2class($scheme)) && + defined($class.'::STAT_RETURN_MIME_TYPE') && + ($mime_attr = constant($class.'::STAT_RETURN_MIME_TYPE'))) + { + $inst = new $class; + $stat = $inst->url_stat(self::parse_url($url,PHP_URL_PATH),0); + if ($stat && $stat[$mime_attr]) + { + $mime = $stat[$mime_attr]; + } + } + } + if (!$mime && is_dir($url)) + { + $mime = self::DIR_MIME_TYPE; + } + // if we operate on the regular filesystem and the mime_content_type function is available --> use it + if (!$mime && !$scheme && function_exists('mime_content_type')) + { + $mime = mime_content_type($path); + } + // using EGw's own mime magic (currently only checking the extension!) + if (!$mime) + { + $mime = MimeMagic::filename2mime(self::parse_url($url,PHP_URL_PATH)); + } + //error_log(__METHOD__."($path,$recheck) mime=$mime"); + return $mime; + } + + /** + * Get the class-name for a scheme + * + * A scheme is not allowed to contain an underscore, but allows a dot and a class names only allow or need underscores, but no dots + * --> we replace dots in scheme with underscored to get the class-name + * + * @param string $scheme eg. vfs + * @return string + */ + static function scheme2class($scheme) + { + return Vfs\StreamWrapper::scheme2class($scheme); + } + + /** + * Clears our internal stat and symlink cache + * + * Normaly not necessary, as it is automatically cleared/updated, UNLESS Vfs::$user changes! + * + * We have to clear the symlink cache before AND after calling the backend, + * because auf traversal rights may be different when Vfs::$user changes! + * + * @param string $path ='/' path of backend, whos cache to clear + */ + static function clearstatcache($path='/') + { + //error_log(__METHOD__."('$path')"); + Vfs\StreamWrapper::clearstatcache($path); + self::_call_on_backend('clearstatcache', array($path), true, 0); + Vfs\StreamWrapper::clearstatcache($path); + } } Vfs::init_static(); diff --git a/api/src/Vfs/Dav/File.php b/api/src/Vfs/Dav/File.php index 78a52d00b9..28472c836c 100644 --- a/api/src/Vfs/Dav/File.php +++ b/api/src/Vfs/Dav/File.php @@ -66,7 +66,7 @@ class File extends DAV\FS\File */ function getETag() { - if (($stat = Vfs::url_stat($this->vfs_path, STREAM_URL_STAT_QUIET))) + if (($stat = Vfs::stat($this->vfs_path, STREAM_URL_STAT_QUIET))) { return '"'.$stat['ino'].':'.$stat['mtime'].':'.$stat['size'].'"'; } diff --git a/api/src/Vfs/Filesystem/StreamWrapper.php b/api/src/Vfs/Filesystem/StreamWrapper.php index b39b08d352..0c22a70846 100644 --- a/api/src/Vfs/Filesystem/StreamWrapper.php +++ b/api/src/Vfs/Filesystem/StreamWrapper.php @@ -306,7 +306,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface * @param string $url * @return boolean TRUE on success or FALSE on failure */ - static function unlink ( $url ) + function unlink ( $url ) { $path = Vfs::decodePath(Vfs::parse_url($url,PHP_URL_PATH)); @@ -331,7 +331,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface * @param string $url_to * @return boolean TRUE on success or FALSE on failure */ - static function rename ( $url_from, $url_to ) + function rename ( $url_from, $url_to ) { $from = Vfs::parse_url($url_from); $to = Vfs::parse_url($url_to); @@ -381,7 +381,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE * @return boolean TRUE on success or FALSE on failure */ - static function mkdir ( $url, $mode, $options ) + function mkdir ( $url, $mode, $options ) { unset($mode); // not used, but required by interface @@ -415,7 +415,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface * @param int $options Possible values include STREAM_REPORT_ERRORS. * @return boolean TRUE on success or FALSE on failure. */ - static function rmdir ( $url, $options ) + function rmdir ( $url, $options ) { unset($options); // not used, but required by interface @@ -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); } /** @@ -552,7 +517,7 @@ class StreamWrapper implements Vfs\StreamWrapperIface * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! * @return array */ - static function url_stat ( $url, $flags ) + function url_stat ( $url, $flags ) { $parts = Vfs::parse_url($url); $path = Vfs::decodePath($parts['path']); diff --git a/api/src/Vfs/Links/StreamWrapper.php b/api/src/Vfs/Links/StreamWrapper.php index 9564a3aa62..e33d5595dd 100644 --- a/api/src/Vfs/Links/StreamWrapper.php +++ b/api/src/Vfs/Links/StreamWrapper.php @@ -137,7 +137,7 @@ class StreamWrapper extends LinksParent * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! * @return array */ - static function url_stat ( $url, $flags ) + function url_stat ( $url, $flags ) { $eacl_check=self::check_extended_acl($url,Vfs::READABLE); @@ -232,7 +232,7 @@ class StreamWrapper extends LinksParent * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE, we allways use recursive! * @return boolean TRUE on success or FALSE on failure */ - static function mkdir($path,$mode,$options) + function mkdir($path,$mode,$options) { unset($mode); // not used, but required by function signature @@ -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 ab93cd8c08..caf77b9fd4 100644 --- a/api/src/Vfs/Sqlfs/StreamWrapper.php +++ b/api/src/Vfs/Sqlfs/StreamWrapper.php @@ -543,7 +543,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param string $url * @return boolean TRUE on success or FALSE on failure */ - static function unlink ( $url, $parent_stat=null ) + function unlink ( $url, $parent_stat=null ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); @@ -553,10 +553,10 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface if (!isset($parent_stat)) { $parent_stat = !($dir = Vfs::dirname($path)) ? false : - static::url_stat($dir, STREAM_URL_STAT_LINK); + $this->url_stat($dir, STREAM_URL_STAT_LINK); } - if (!$parent_stat || !($stat = self::url_stat($path,STREAM_URL_STAT_LINK)) || + if (!$parent_stat || !($stat = $this->url_stat($path,STREAM_URL_STAT_LINK)) || !$dir || !Vfs::check_access($dir, Vfs::WRITABLE, $parent_stat)) { self::_remove_password($url); @@ -599,7 +599,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param string $url_to * @return boolean TRUE on success or FALSE on failure */ - static function rename ( $url_from, $url_to) + function rename ( $url_from, $url_to) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url_from,$url_to)"); @@ -693,13 +693,13 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE * @return boolean TRUE on success or FALSE on failure */ - static function mkdir ( $url, $mode, $options ) + function mkdir ( $url, $mode, $options ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)"); if (self::LOG_LEVEL > 1) error_log(__METHOD__." called from:".function_backtrace()); $path = Vfs::parse_url($url,PHP_URL_PATH); - if (self::url_stat($path,STREAM_URL_STAT_QUIET)) + if ($this->url_stat($path,STREAM_URL_STAT_QUIET)) { self::_remove_password($url); if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) already exist!"); @@ -720,7 +720,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface return false; } if (($query = Vfs::parse_url($url,PHP_URL_QUERY))) $parent_path .= '?'.$query; - $parent = self::url_stat($parent_path,STREAM_URL_STAT_QUIET); + $parent = $this->url_stat($parent_path,STREAM_URL_STAT_QUIET); // check if we should also create all non-existing path components and our parent does not exist, // if yes call ourself recursive with the parent directory @@ -731,7 +731,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface { return false; } - $parent = self::url_stat($parent_path,0); + $parent = $this->url_stat($parent_path,0); } if (!$parent || !Vfs::check_access($parent_path,Vfs::WRITABLE,$parent)) { @@ -788,14 +788,14 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param int $options Possible values include STREAM_REPORT_ERRORS. * @return boolean TRUE on success or FALSE on failure. */ - static function rmdir ( $url, $options ) + function rmdir ( $url, $options ) { if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)"); $path = Vfs::parse_url($url,PHP_URL_PATH); if (!($parent = Vfs::dirname($path)) || - !($stat = self::url_stat($path, 0)) || $stat['mime'] != self::DIR_MIME_TYPE || + !($stat = $this->url_stat($path, 0)) || $stat['mime'] != self::DIR_MIME_TYPE || !Vfs::check_access($parent, Vfs::WRITABLE, static::url_stat($parent,0))) { self::_remove_password($url); @@ -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,14 +885,15 @@ 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)"); $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($path,STREAM_URL_STAT_QUIET))) { // file does not exist --> create an empty one if (!($f = fopen(self::SCHEME.'://default'.$path,'w')) || !fclose($f)) @@ -860,7 +904,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface { return true; // new (empty) file created with current mod time } - $stat = self::url_stat($path,0); + $stat = $vfs->url_stat($path,0); } unset(self::$stat_cache[$path]); $stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_modified=:fs_modified,fs_modifier=:fs_modifier WHERE fs_id=:fs_id'); @@ -879,13 +923,14 @@ 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)"); $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,0))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($path,0))) { if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no such file or directory!"); trigger_error("No such file or directory $url !",E_USER_WARNING); @@ -923,13 +968,14 @@ 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)"); $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,0))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($path,0))) { if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no such file or directory!"); trigger_error("No such file or directory $url !",E_USER_WARNING); @@ -968,13 +1014,14 @@ 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)"); $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($path,0))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($path,0))) { if (self::LOG_LEVEL) error_log(__METHOD__."($url, $mode) no such file or directory!"); trigger_error("No such file or directory $url !",E_USER_WARNING); @@ -1018,7 +1065,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface $path = Vfs::parse_url($url,PHP_URL_PATH); - if (!($stat = self::url_stat($url,0)) || // dir not found + if (!($stat = $this->url_stat($url,0)) || // dir not found $stat['mime'] != self::DIR_MIME_TYPE || // no dir !Vfs::check_access($url,Vfs::EXECUTABLE|Vfs::READABLE,$stat)) // no access { @@ -1078,7 +1125,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface * @param boolean $eacl_access =null allows extending classes to pass the value of their check_extended_acl() method (no lsb!) * @return array */ - static function url_stat ( $url, $flags, $eacl_access=null ) + function url_stat ( $url, $flags, $eacl_access=null ) { static $max_subquery_depth=null; if (is_null($max_subquery_depth)) @@ -1187,7 +1234,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface error_log(__METHOD__."() decremented max_subquery_depth to $max_subquery_depth"); Api\Config::save_value('max_subquery_depth', $max_subquery_depth, 'phpgwapi'); if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) $GLOBALS['egw']->invalidate_session_cache(); - return self::url_stat($url, $flags, $eacl_access); + return $this->url_stat($url, $flags, $eacl_access); } self::$stat_cache[$path] = $info; @@ -1284,7 +1331,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface */ static function readlink($path) { - $link = !($lstat = self::url_stat($path,STREAM_URL_STAT_LINK)) || is_null($lstat['readlink']) ? false : $lstat['readlink']; + $vfs = new self(); + $link = !($lstat = $vfs->url_stat($path,STREAM_URL_STAT_LINK)) || is_null($lstat['readlink']) ? false : $lstat['readlink']; if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = $link"); @@ -1302,7 +1350,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface { if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$target','$link')"); - if (self::url_stat($link,0)) + $vfs = new self(); + if ($vfs->url_stat($link,0)) { if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$target','$link') $link exists, returning false!"); return false; // $link already exists @@ -1422,7 +1471,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface } if (is_null($fs_id)) { - if (!($stat = self::url_stat($path,0))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($path,0))) { if (self::LOG_LEVEL) error_log(__METHOD__."($path,$rights,$owner,$fs_id) no such file or directory!"); return false; // $path not found @@ -1740,7 +1790,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface if (self::LOG_LEVEL > 1) error_log(__METHOD__."(".array2string($path).','.array2string($props)); if (!is_numeric($path)) { - if (!($stat = self::url_stat($path,0))) + if (!($stat = $vfs->url_stat($path,0))) { return false; } @@ -1807,7 +1857,8 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface { if (!is_numeric($id)) { - if (!($stat = self::url_stat($id,0))) + $vfs = new self(); + if (!($stat = $vfs->url_stat($id,0))) { if (self::LOG_LEVEL) error_log(__METHOD__."(".array2string($path_ids).",$ns) path '$id' not found!"); return false; diff --git a/api/src/Vfs/Sqlfs/Utils.php b/api/src/Vfs/Sqlfs/Utils.php index 65857a8bbe..f303328032 100644 --- a/api/src/Vfs/Sqlfs/Utils.php +++ b/api/src/Vfs/Sqlfs/Utils.php @@ -150,9 +150,10 @@ class Utils extends StreamWrapper ); $stmt = $delete_stmt = null; $msgs = array(); + $sqlfs = Vfs\Sqlfs(); foreach($dirs as $path => $id) { - if (!($stat = self::url_stat($path, STREAM_URL_STAT_LINK))) + if (!($stat = $sqlfs->url_stat($path, STREAM_URL_STAT_LINK))) { if ($check_only) { @@ -308,6 +309,7 @@ class Utils extends StreamWrapper { $lostnfound = null; $msgs = array(); + $sqlfs = Vfs\Sqlfs(); foreach(self::$pdo->query('SELECT fs.* FROM '.self::TABLE.' fs'. ' LEFT JOIN '.self::TABLE.' dir ON dir.fs_id=fs.fs_dir'. ' WHERE fs.fs_id > 1 AND dir.fs_id IS NULL') as $row) @@ -322,13 +324,13 @@ class Utils extends StreamWrapper if (!isset($lostnfound)) { // check if we already have /lost+found, create it if not - if (!($lostnfound = self::url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) + if (!($lostnfound = $sqlfs->url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) { Vfs::$is_root = true; if (!self::mkdir(self::LOST_N_FOUND, self::LOST_N_FOUND_MOD, 0) || !(!($admins = $GLOBALS['egw']->accounts->name2id(self::LOST_N_FOUND_GRP)) || self::chgrp(self::LOST_N_FOUND, $admins) && self::chmod(self::LOST_N_FOUND,self::LOST_N_FOUND_MOD)) || - !($lostnfound = self::url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) + !($lostnfound = $sqlfs->url_stat(self::LOST_N_FOUND, STREAM_URL_STAT_QUIET))) { $msgs[] = lang("Can't create directory %1 to connect found unconnected nodes to it!",self::LOST_N_FOUND); } diff --git a/api/src/Vfs/StreamWrapper.php b/api/src/Vfs/StreamWrapper.php index ab9c1329fd..10b36ab9c9 100644 --- a/api/src/Vfs/StreamWrapper.php +++ b/api/src/Vfs/StreamWrapper.php @@ -157,11 +157,11 @@ class StreamWrapper implements StreamWrapperIface * @param array|boolean &$stat=null on return: stat of existing file or false for non-existing files * @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry */ - static function resolve_url_symlinks($_path,$file_exists=true,$resolve_last_symlink=true,&$stat=null) + function resolve_url_symlinks($_path,$file_exists=true,$resolve_last_symlink=true,&$stat=null) { $path = self::get_path($_path); - if (!($stat = self::url_stat($path,$resolve_last_symlink?0:STREAM_URL_STAT_LINK)) && !$file_exists) + if (!($stat = $this->url_stat($path,$resolve_last_symlink?0:STREAM_URL_STAT_LINK)) && !$file_exists) { $url = null; $stat = self::check_symlink_components($path,0,$url); @@ -172,9 +172,9 @@ class StreamWrapper implements StreamWrapperIface $url = $stat['url']; } // if the url resolves to a symlink to the vfs, resolve this vfs:// url direct - if ($url && self::parse_url($url,PHP_URL_SCHEME) == self::SCHEME) + if ($url && Vfs::parse_url($url,PHP_URL_SCHEME) == self::SCHEME) { - $url = self::resolve_url(self::parse_url($url,PHP_URL_PATH)); + $url = self::resolve_url(Vfs::parse_url($url,PHP_URL_PATH)); } if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,file_exists=$file_exists,resolve_last_symlink=$resolve_last_symlink) = '$url'$log"); return $url; @@ -223,7 +223,7 @@ class StreamWrapper implements StreamWrapperIface 'home' => str_replace(array('\\\\','\\'),array('','/'),$GLOBALS['egw_info']['user']['homedirectory']), ); } - $parts = array_merge(self::parse_url($path),$defaults); + $parts = array_merge(Vfs::parse_url($path),$defaults); if (!$parts['host']) $parts['host'] = 'default'; // otherwise we get an invalid url (scheme:///path/to/something)! if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME) @@ -237,7 +237,7 @@ class StreamWrapper implements StreamWrapperIface { if ($mounted == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1)) { - $scheme = self::parse_url($url,PHP_URL_SCHEME); + $scheme = Vfs::parse_url($url,PHP_URL_SCHEME); if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers)) { self::load_wrapper($scheme); @@ -316,7 +316,7 @@ class StreamWrapper implements StreamWrapperIface $this->opened_stream = null; $stat = null; - if (!($url = self::resolve_url_symlinks($path,$mode[0]=='r',true,$stat))) + if (!($url = $this->resolve_url_symlinks($path,$mode[0]=='r',true,$stat))) { return false; } @@ -330,7 +330,7 @@ class StreamWrapper implements StreamWrapperIface return false; } $this->opened_stream_mode = $mode; - $this->opened_stream_path = $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH); + $this->opened_stream_path = $path[0] == '/' ? $path : Vfs::parse_url($path, PHP_URL_PATH); $this->opened_stream_url = $url; $this->opened_stream_is_new = !$stat; @@ -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. * @@ -489,9 +538,9 @@ class StreamWrapper implements StreamWrapperIface * @param string $path * @return boolean TRUE on success or FALSE on failure */ - static function unlink ( $path ) + function unlink ( $path ) { - if (!($url = self::resolve_url_symlinks($path,true,false))) // true,false file need to exist, but do not resolve last component + if (!($url = $this->resolve_url_symlinks($path,true,false))) // true,false file need to exist, but do not resolve last component { return false; } @@ -499,7 +548,7 @@ class StreamWrapper implements StreamWrapperIface { return false; } - $stat = self::url_stat($path, STREAM_URL_STAT_LINK); + $stat = $this->url_stat($path, STREAM_URL_STAT_LINK); self::symlinkCache_remove($path); $ok = unlink($url); @@ -509,7 +558,7 @@ class StreamWrapper implements StreamWrapperIface { Api\Hooks::process(array( 'location' => 'vfs_unlink', - 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : Vfs::parse_url($path, PHP_URL_PATH), 'url' => $url, 'stat' => $stat, ),'',true); @@ -529,15 +578,15 @@ class StreamWrapper implements StreamWrapperIface * @param string $path_to * @return boolean TRUE on success or FALSE on failure */ - static function rename ( $path_from, $path_to ) + function rename ( $path_from, $path_to ) { - if (!($url_from = self::resolve_url_symlinks($path_from,true,false)) || - !($url_to = self::resolve_url_symlinks($path_to,false))) + if (!($url_from = $this->resolve_url_symlinks($path_from,true,false)) || + !($url_to = $this->resolve_url_symlinks($path_to,false))) { return false; } // if file is moved from one filesystem / wrapper to an other --> copy it (rename fails cross wrappers) - if (self::parse_url($url_from,PHP_URL_SCHEME) == self::parse_url($url_to,PHP_URL_SCHEME)) + if (Vfs::parse_url($url_from,PHP_URL_SCHEME) == Vfs::parse_url($url_to,PHP_URL_SCHEME)) { self::symlinkCache_remove($path_from); $ret = rename($url_from,$url_to); @@ -547,7 +596,7 @@ class StreamWrapper implements StreamWrapperIface $ret = stream_copy_to_stream($from,$to) !== false; fclose($from); fclose($to); - if ($ret) self::unlink($path_from); + if ($ret) $this->unlink($path_from); } else { @@ -562,8 +611,8 @@ class StreamWrapper implements StreamWrapperIface { Api\Hooks::process(array( 'location' => 'vfs_rename', - 'from' => $path_from[0] == '/' ? $path_from : self::parse_url($path_from, PHP_URL_PATH), - 'to' => $path_to[0] == '/' ? $path_to : self::parse_url($path_to, PHP_URL_PATH), + 'from' => $path_from[0] == '/' ? $path_from : Vfs::parse_url($path_from, PHP_URL_PATH), + 'to' => $path_to[0] == '/' ? $path_to : Vfs::parse_url($path_to, PHP_URL_PATH), 'url_from' => $url_from, 'url_to' => $url_to, ),'',true); @@ -582,9 +631,9 @@ class StreamWrapper implements StreamWrapperIface * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE * @return boolean TRUE on success or FALSE on failure */ - static function mkdir ( $path, $mode, $options ) + function mkdir ( $path, $mode, $options ) { - if (!($url = self::resolve_url_symlinks($path,false))) // false = directory does not need to exists + if (!($url = $this->resolve_url_symlinks($path,false))) // false = directory does not need to exists { return false; } @@ -595,7 +644,7 @@ class StreamWrapper implements StreamWrapperIface { Api\Hooks::process(array( 'location' => 'vfs_mkdir', - 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : Vfs::parse_url($path, PHP_URL_PATH), 'url' => $url, ),'',true); } @@ -612,10 +661,10 @@ class StreamWrapper implements StreamWrapperIface * @param int $options Possible values include STREAM_REPORT_ERRORS. * @return boolean TRUE on success or FALSE on failure. */ - static function rmdir ( $path, $options ) + function rmdir ( $path, $options ) { unset($options); // not uses but required by function signature - if (!($url = self::resolve_url_symlinks($path))) + if (!($url = $this->resolve_url_symlinks($path))) { return false; } @@ -623,7 +672,7 @@ class StreamWrapper implements StreamWrapperIface { return false; } - $stat = self::url_stat($path, STREAM_URL_STAT_LINK); + $stat = $this->url_stat($path, STREAM_URL_STAT_LINK); self::symlinkCache_remove($path); $ok = rmdir($url); @@ -633,7 +682,7 @@ class StreamWrapper implements StreamWrapperIface { Api\Hooks::process(array( 'location' => 'vfs_rmdir', - 'path' => $path[0] == '/' ? $path : self::parse_url($path, PHP_URL_PATH), + 'path' => $path[0] == '/' ? $path : Vfs::parse_url($path, PHP_URL_PATH), 'url' => $url, 'stat' => $stat, ),'',true); @@ -641,217 +690,6 @@ class StreamWrapper implements StreamWrapperIface return $ok; } - /** - * Allow to call methods of the underlying stream wrapper: touch, chmod, chgrp, chown, ... - * - * We cant use a magic __call() method, as it does not work for static methods! - * - * @param string $name - * @param array $params first param has to be the path, otherwise we can not determine the correct wrapper - * @param boolean $fail_silent =false should only false be returned if function is not supported by the backend, - * or should an E_USER_WARNING error be triggered (default) - * @param int $path_param_key =0 key in params containing the path, default 0 - * @return mixed return value of backend or false if function does not exist on backend - */ - static protected function _call_on_backend($name,$params,$fail_silent=false,$path_param_key=0) - { - $pathes = $params[$path_param_key]; - - $scheme2urls = array(); - foreach(is_array($pathes) ? $pathes : array($pathes) as $path) - { - if (!($url = self::resolve_url_symlinks($path,false,false))) - { - return false; - } - $k=(string)self::parse_url($url,PHP_URL_SCHEME); - if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array(); - $scheme2urls[$k][$path] = $url; - } - $ret = array(); - foreach($scheme2urls as $scheme => $urls) - { - if ($scheme) - { - if (!class_exists($class = self::scheme2class($scheme)) || !method_exists($class,$name)) - { - if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING); - return false; - } - if (!is_array($pathes)) - { - $params[$path_param_key] = $url; - - return call_user_func_array(array($class,$name),$params); - } - $params[$path_param_key] = $urls; - if (!is_array($r = call_user_func_array(array($class,$name),$params))) - { - return $r; - } - // we need to re-translate the urls to pathes, as they can eg. contain symlinks - foreach($urls as $path => $url) - { - if (isset($r[$url]) || isset($r[$url=self::parse_url($url,PHP_URL_PATH)])) - { - $ret[$path] = $r[$url]; - } - } - } - // call the filesystem specific function (dont allow to use arrays!) - elseif(!function_exists($name) || is_array($pathes)) - { - return false; - } - else - { - $time = null; - return $name($url,$time); - } - } - return $ret; - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * @param string $path - * @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 - */ - static function touch($path,$time=null,$atime=null) - { - return self::_call_on_backend('touch',array($path,$time,$atime)); - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Requires owner or 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) - { - return self::_call_on_backend('chmod',array($path,$mode)); - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Requires root rights! - * - * @param string $path - * @param int $owner numeric user id - * @return boolean true on success, false otherwise - */ - static function chown($path,$owner) - { - return self::_call_on_backend('chown',array($path,$owner)); - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * Requires owner or root rights! - * - * @param string $path - * @param int $group numeric group id - * @return boolean true on success, false otherwise - */ - static function chgrp($path,$group) - { - return self::_call_on_backend('chgrp',array($path,$group)); - } - - /** - * Returns the target of a symbolic link - * - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * @param string $path - * @return string|boolean link-data or false if no link - */ - static function readlink($path) - { - $ret = self::_call_on_backend('readlink',array($path),true); // true = fail silent, if backend does not support readlink - //error_log(__METHOD__."('$path') returning ".array2string($ret).' '.function_backtrace()); - return $ret; - } - - /** - * Creates a symbolic link - * - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * @param string $target target of the link - * @param string $link path of the link to create - * @return boolean true on success, false on error - */ - static function symlink($target,$link) - { - if (($ret = self::_call_on_backend('symlink',array($target,$link),false,1))) // 1=path is in $link! - { - self::symlinkCache_remove($link); - } - return $ret; - } - - /** - * This is not (yet) a stream-wrapper function, but it's necessary and can be used static - * - * The methods use the following ways to get the mime type (in that order) - * - directories (is_dir()) --> self::DIR_MIME_TYPE - * - stream implemented by class defining the STAT_RETURN_MIME_TYPE constant --> use mime-type returned by url_stat - * - for regular filesystem use mime_content_type function if available - * - use eGW's mime-magic class - * - * @param string $path - * @param boolean $recheck =false true = do a new check, false = rely on stored mime type (if existing) - * @return string mime-type (self::DIR_MIME_TYPE for directories) - */ - static function mime_content_type($path,$recheck=false) - { - if (!($url = self::resolve_url_symlinks($path))) - { - return false; - } - if (($scheme = self::parse_url($url,PHP_URL_SCHEME)) && !$recheck) - { - // check it it's an eGW stream wrapper returning mime-type via url_stat - // we need to first check if the constant is defined, as we get a fatal error in php5.3 otherwise - if (class_exists($class = self::scheme2class($scheme)) && - defined($class.'::STAT_RETURN_MIME_TYPE') && - ($mime_attr = constant($class.'::STAT_RETURN_MIME_TYPE'))) - { - $stat = call_user_func(array($class,'url_stat'),self::parse_url($url,PHP_URL_PATH),0); - if ($stat && $stat[$mime_attr]) - { - $mime = $stat[$mime_attr]; - } - } - } - if (!$mime && is_dir($url)) - { - $mime = self::DIR_MIME_TYPE; - } - // if we operate on the regular filesystem and the mime_content_type function is available --> use it - if (!$mime && !$scheme && function_exists('mime_content_type')) - { - $mime = mime_content_type($path); - } - // using EGw's own mime magic (currently only checking the extension!) - if (!$mime) - { - $mime = Api\MimeMagic::filename2mime(self::parse_url($url,PHP_URL_PATH)); - } - //error_log(__METHOD__."($path,$recheck) mime=$mime"); - return $mime; - } - /** * This method is called immediately when your stream object is created for examining directory contents with opendir(). * @@ -864,7 +702,7 @@ class StreamWrapper implements StreamWrapperIface $this->dir_url_params = array(); $this->extra_dir_ptr = 0; - if (!($this->opened_dir_url = self::resolve_url_symlinks($path))) + if (!($this->opened_dir_url = $this->resolve_url_symlinks($path))) { if (self::LOG_LEVEL > 0) error_log(__METHOD__."( $path,$options) resolve_url_symlinks() failed!"); return false; @@ -877,7 +715,7 @@ class StreamWrapper implements StreamWrapperIface } $this->opened_dir_writable = Vfs::check_access($this->opened_dir_url,Vfs::WRITABLE); // check our fstab if we need to add some of the mountpoints - $basepath = self::parse_url($path,PHP_URL_PATH); + $basepath = Vfs::parse_url($path,PHP_URL_PATH); foreach(array_keys(self::$fstab) as $mounted) { if (((Vfs::dirname($mounted) == $basepath || Vfs::dirname($mounted).'/' == $basepath) && $mounted != '/') && @@ -922,7 +760,7 @@ class StreamWrapper implements StreamWrapperIface * @param boolean $check_symlink_components =true check if path contains symlinks in path components other then the last one * @return array */ - static function url_stat ( $path, $flags, $try_create_home=false, $check_symlink_components=true, $check_symlink_depth=self::MAX_SYMLINK_DEPTH, $try_reconnect=true ) + function url_stat ( $path, $flags, $try_create_home=false, $check_symlink_components=true, $check_symlink_depth=self::MAX_SYMLINK_DEPTH, $try_reconnect=true ) { if (!($url = self::resolve_url($path,!($flags & STREAM_URL_STAT_LINK), $check_symlink_components))) { @@ -946,17 +784,17 @@ class StreamWrapper implements StreamWrapperIface if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path',$flags) maximum symlink depth exceeded, might be a circular symlink!"); $stat = false; } - elseif (($lpath = self::readlink($url))) + elseif (($lpath = Vfs::readlink($url))) { if ($lpath[0] != '/') // concat relative path { - $lpath = Vfs::concat(self::parse_url($path,PHP_URL_PATH),'../'.$lpath); + $lpath = Vfs::concat(Vfs::parse_url($path,PHP_URL_PATH),'../'.$lpath); } $u_query = parse_url($url,PHP_URL_QUERY); $url = Vfs::PREFIX.$lpath; if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$flags) symlif (substr($path,-1) == '/' && $path != '/') $path = substr($path,0,-1); // remove trailing slash eg. added by WebDAVink found and resolved to $url"); // try reading the stat of the link - if (($stat = self::url_stat($lpath, STREAM_URL_STAT_QUIET, false, true, $check_symlink_depth-1))) + if (($stat = $this->url_stat($lpath, STREAM_URL_STAT_QUIET, false, true, $check_symlink_depth-1))) { $stat_query = parse_url($stat['url'], PHP_URL_QUERY); if($u_query || $stat_query) @@ -982,13 +820,13 @@ class StreamWrapper implements StreamWrapperIface { // reconnect to db Vfs\Sqlfs\StreamWrapper::reconnect(); - return self::url_stat($path, $flags, $try_create_home, $check_symlink_components, $check_symlink_depth, false); + return $this->url_stat($path, $flags, $try_create_home, $check_symlink_components, $check_symlink_depth, false); } // if numer of tries is exceeded, re-throw exception throw $e; } // check if a failed url_stat was for a home dir, in that case silently create it - if (!$stat && $try_create_home && Vfs::dirname(self::parse_url($path,PHP_URL_PATH)) == '/home' && + if (!$stat && $try_create_home && Vfs::dirname(Vfs::parse_url($path,PHP_URL_PATH)) == '/home' && ($id = $GLOBALS['egw']->accounts->name2id(basename($path))) && $GLOBALS['egw']->accounts->id2name($id) == basename($path)) // make sure path has the right case! { @@ -1000,7 +838,7 @@ class StreamWrapper implements StreamWrapperIface ); call_user_func(array(__NAMESPACE__.'\\Hooks',$hook_data['location']),$hook_data); unset($hook_data); - $stat = self::url_stat($path,$flags,false); + $stat = $this->url_stat($path,$flags,false); } $query = parse_url($url, PHP_URL_QUERY); if (!$stat && $check_symlink_components) // check if there's a symlink somewhere inbetween the path @@ -1043,7 +881,7 @@ class StreamWrapper implements StreamWrapperIface * @param string &$url=null already resolved path * @return array|boolean stat array or false if not found */ - static private function check_symlink_components($path,$flags=0,&$url=null) + private function check_symlink_components($path,$flags=0,&$url=null) { if (is_null($url) && !($url = self::resolve_url($path))) { @@ -1055,20 +893,20 @@ class StreamWrapper implements StreamWrapperIface while (($rel_path = Vfs::basename($url).($rel_path ? '/'.$rel_path : '')) && ($url = Vfs::dirname($url))) { - if (($stat = self::url_stat($url,0,false,false))) + if (($stat = $this->url_stat($url,0,false,false))) { - if (is_link($url) && ($lpath = self::readlink($url))) + if (is_link($url) && ($lpath = Vfs::readlink($url))) { if (self::LOG_LEVEL > 1) $log = "rel_path='$rel_path', url='$url': lpath='$lpath'"; if ($lpath[0] != '/') { - $lpath = Vfs::concat(self::parse_url($url,PHP_URL_PATH),'../'.$lpath); + $lpath = Vfs::concat(Vfs::parse_url($url,PHP_URL_PATH),'../'.$lpath); } //self::symlinkCache_add($path,Vfs::PREFIX.$lpath); $url = Vfs::PREFIX.Vfs::concat($lpath,$rel_path); if (self::LOG_LEVEL > 1) error_log("$log --> lpath='$lpath', url='$url'"); - return self::url_stat($url,$flags); + return $this->url_stat($url,$flags); } $url = Vfs::concat($url,$rel_path); if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path',$flags,'$url') returning null"); @@ -1098,7 +936,7 @@ class StreamWrapper implements StreamWrapperIface if (isset(self::$symlink_cache[$path])) return; // nothing to do - if ($target[0] != '/') $target = self::parse_url($target,PHP_URL_PATH); + if ($target[0] != '/') $target = Vfs::parse_url($target,PHP_URL_PATH); self::$symlink_cache[$path] = $target; @@ -1162,17 +1000,9 @@ class StreamWrapper implements StreamWrapperIface * Clears our internal stat and symlink cache * * Normaly not necessary, as it is automatically cleared/updated, UNLESS Vfs::$user changes! - * - * We have to clear the symlink cache before AND after calling the backend, - * because auf traversal rights may be different when Vfs::$user changes! - * - * @param string $path ='/' path of backend, whos cache to clear */ - static function clearstatcache($path='/') + static function clearstatcache() { - //error_log(__METHOD__."('$path')"); - self::$symlink_cache = self::$resolve_url_cache = array(); - self::_call_on_backend('clearstatcache', array($path), true, 0); self::$symlink_cache = self::$resolve_url_cache = array(); } @@ -1310,59 +1140,6 @@ class StreamWrapper implements StreamWrapperIface } } - /** - * Utf-8 save version of parse_url - * - * Does caching withing request, to not have to parse urls over and over again. - * - * @param string $url - * @param int $component =-1 PHP_URL_* constants - * @return array|string|boolean on success array or string, if $component given, or false on failure - */ - static function parse_url($url, $component=-1) - { - static $component2str = array( - PHP_URL_SCHEME => 'scheme', - PHP_URL_HOST => 'host', - PHP_URL_PORT => 'port', - PHP_URL_USER => 'user', - PHP_URL_PASS => 'pass', - PHP_URL_PATH => 'path', - PHP_URL_QUERY => 'query', - PHP_URL_FRAGMENT => 'fragment', - ); - static $cache = array(); // some caching - - $result =& $cache[$url]; - - if (!isset($result)) - { - // Build arrays of values we need to decode before parsing - static $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); - static $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]"); - static $str_replace = null; - if (!isset($str_replace)) $str_replace = function_exists('mb_str_replace') ? 'mb_str_replace' : 'str_replace'; - - // Create encoded URL with special URL characters decoded so it can be parsed - // All other characters will be encoded - $encodedURL = $str_replace($entities, $replacements, urlencode($url)); - - // Parse the encoded URL - $result = $encodedParts = parse_url($encodedURL); - - // Now, decode each value of the resulting array - if ($encodedParts) - { - $result = array(); - foreach ($encodedParts as $key => $value) - { - $result[$key] = urldecode($str_replace($replacements, $entities, $value)); - } - } - } - return $component >= 0 ? $result[$component2str[$component]] : $result; - } - /** * Getting the path from an url (or path) AND removing trailing slashes * @@ -1372,14 +1149,14 @@ class StreamWrapper implements StreamWrapperIface */ static protected function get_path($path,$only_remove_scheme=self::SCHEME) { - if ($path[0] != '/' && (!$only_remove_scheme || self::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme)) + if ($path[0] != '/' && (!$only_remove_scheme || Vfs::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme)) { - $path = self::parse_url($path, PHP_URL_PATH); + $path = Vfs::parse_url($path, PHP_URL_PATH); } // remove trailing slashes eg. added by WebDAV, but do NOT remove / from "sqlfs://default/"! if ($path != '/') { - while (mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || self::parse_url($path, PHP_URL_PATH) != '/')) + while (mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || Vfs::parse_url($path, PHP_URL_PATH) != '/')) { $path = mb_substr($path,0,-1); } @@ -1405,6 +1182,116 @@ class StreamWrapper implements StreamWrapperIface return $ret; } + /** + * Mounts $url under $path in the vfs, called without parameter it returns the fstab + * + * The fstab is stored in the eGW configuration and used for all eGW users. + * + * @param string $url =null url of the filesystem to mount, eg. oldvfs://default/ + * @param string $path =null path to mount the filesystem in the vfs, eg. / + * @param boolean $check_url =null check if url is an existing directory, before mounting it + * default null only checks if url does not contain a $ as used in $user or $pass + * @param boolean $persitent_mount =true create a persitent mount, or only a temprary for current request + * @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount + * @return array|boolean array with fstab, if called without parameter or true on successful mount + */ + static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false) + { + if (is_null($check_url)) $check_url = strpos($url,'$') === false; + + if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup + { + $api_config = Config::read('phpgwapi'); + if (isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab'])) + { + self::$fstab = $api_config['vfs_fstab']; + } + else + { + self::$fstab = array( + '/' => 'sqlfs://$host/', + '/apps' => 'links://$host/apps', + ); + } + unset($api_config); + } + if (is_null($url) || is_null($path)) + { + if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns '.array2string(self::$fstab)); + return self::$fstab; + } + if (!self::$is_root) + { + if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!'); + return false; // only root can mount + } + if ($clear_fstab) + { + self::$fstab = array(); + } + if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url) + { + if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.'); + return true; // already mounted + } + self::load_wrapper(Vfs::parse_url($url,PHP_URL_SCHEME)); + + if ($check_url && (!file_exists($url) || opendir($url) === false)) + { + if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') url does NOT exist!'); + return false; // url does not exist + } + self::$fstab[$path] = $url; + + uksort(self::$fstab, function($a, $b) + { + return strlen($a) - strlen($b); + }); + + if ($persitent_mount) + { + Config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); + $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; + // invalidate session cache + if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited + { + $GLOBALS['egw']->invalidate_session_cache(); + } + } + if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns true (successful new mount).'); + return true; + } + + /** + * Unmounts a filesystem part of the vfs + * + * @param string $path url or path of the filesystem to unmount + */ + static function umount($path) + { + if (!self::$is_root) + { + if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).','.array2string($path).') permission denied, you are NOT root!'); + return false; // only root can mount + } + if (!isset(self::$fstab[$path]) && ($path = array_search($path,self::$fstab)) === false) + { + if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).') NOT mounted!'); + return false; // $path not mounted + } + unset(self::$fstab[$path]); + + Config::save_value('vfs_fstab',self::$fstab,'phpgwapi'); + $GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab; + // invalidate session cache + if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited + { + $GLOBALS['egw']->invalidate_session_cache(); + } + if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($path).') returns true (successful unmount).'); + return true; + } + /** * Init our static properties and register this wrapper * diff --git a/api/src/Vfs/StreamWrapperIface.php b/api/src/Vfs/StreamWrapperIface.php index e9ac4e05b9..0003086127 100644 --- a/api/src/Vfs/StreamWrapperIface.php +++ b/api/src/Vfs/StreamWrapperIface.php @@ -143,7 +143,7 @@ interface StreamWrapperIface * @param string $path * @return boolean TRUE on success or FALSE on failure */ - static function unlink ( $path ); + function unlink ( $path ); /** * This method is called in response to rename() calls on URL paths associated with the wrapper. @@ -157,7 +157,7 @@ interface StreamWrapperIface * @param string $path_to * @return boolean TRUE on success or FALSE on failure */ - static function rename ( $path_from, $path_to ); + function rename ( $path_from, $path_to ); /** * This method is called in response to mkdir() calls on URL paths associated with the wrapper. @@ -170,7 +170,7 @@ interface StreamWrapperIface * @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE * @return boolean TRUE on success or FALSE on failure */ - static function mkdir ( $path, $mode, $options ); + function mkdir ( $path, $mode, $options ); /** * This method is called in response to rmdir() calls on URL paths associated with the wrapper. @@ -182,7 +182,7 @@ interface StreamWrapperIface * @param int $options Possible values include STREAM_REPORT_ERRORS. * @return boolean TRUE on success or FALSE on failure. */ - static function rmdir ( $path, $options ); + function rmdir ( $path, $options ); /** * This method is called immediately when your stream object is created for examining directory contents with opendir(). @@ -218,7 +218,7 @@ interface StreamWrapperIface * stat triggers it's own warning anyway, so it makes no sense to trigger one by our stream-wrapper! * @return array */ - static function url_stat ( $path, $flags ); + function url_stat ( $path, $flags ); /** * This method is called in response to readdir(). diff --git a/doc/rpm-build/post_install.php b/doc/rpm-build/post_install.php index bc3ab665d9..bea2835f76 100755 --- a/doc/rpm-build/post_install.php +++ b/doc/rpm-build/post_install.php @@ -423,10 +423,12 @@ else // fix egw_cache evtl. created by root, stoping webserver from accessing it fix_perms(); - // restart running Apache, to force APC to update changed sources and/or Apache configuration - $output = array(); - run_cmd(build_cmd('start_webserver', 'status').' && '.build_cmd('start_webserver', 'restart'), $output, true); - + if (!empty($config['start_webserver'])) + { + // restart running Apache, to force APC to update changed sources and/or Apache configuration + $output = array(); + run_cmd(build_cmd('start_webserver', 'status').' && '.build_cmd('start_webserver', 'restart'), $output, true); + } exit($ret); } @@ -577,7 +579,7 @@ function fix_perms() { global $config; - if (file_exists('/tmp/egw_cache')) + if (file_exists('/tmp/egw_cache') && !empty($config['webserver_user'])) { system('/bin/chown -R '.$config['webserver_user'].' /tmp/egw_cache'); system('/bin/chmod 700 /tmp/egw_cache');