1<?php 2/** 3 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 4 * @author Jesús Macias <jmacias@solidgear.es> 5 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 6 * @author Juan Pablo Villafañez <jvillafanez@solidgear.es> 7 * @author Michael Gapczynski <GapczynskiM@gmail.com> 8 * @author Morris Jobke <hey@morrisjobke.de> 9 * @author Philipp Kapfer <philipp.kapfer@gmx.at> 10 * @author Robin Appelman <icewind@owncloud.com> 11 * @author Robin McCorkell <robin@mccorkell.me.uk> 12 * @author Thomas Müller <thomas.mueller@tmit.eu> 13 * @author Vincent Petry <pvince81@owncloud.com> 14 * 15 * @copyright Copyright (c) 2018, ownCloud GmbH 16 * @license AGPL-3.0 17 * 18 * This code is free software: you can redistribute it and/or modify 19 * it under the terms of the GNU Affero General Public License, version 3, 20 * as published by the Free Software Foundation. 21 * 22 * This program is distributed in the hope that it will be useful, 23 * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 * GNU Affero General Public License for more details. 26 * 27 * You should have received a copy of the GNU Affero General Public License, version 3, 28 * along with this program. If not, see <http://www.gnu.org/licenses/> 29 * 30 */ 31 32namespace OCA\Files_External\Lib\Storage; 33 34use Icewind\SMB\Exception\AlreadyExistsException; 35use Icewind\SMB\Exception\ConnectException; 36use Icewind\SMB\Exception\Exception; 37use Icewind\SMB\Exception\ForbiddenException; 38use Icewind\SMB\Exception\NotFoundException; 39use Icewind\SMB\BasicAuth; 40use Icewind\SMB\IFileInfo; 41use Icewind\SMB\IServer; 42use Icewind\SMB\Native\NativeServer; 43use Icewind\SMB\Wrapped\FileInfo; 44use Icewind\SMB\ServerFactory; 45use Icewind\SMB\System; 46use Icewind\SMB\IShare; 47use Icewind\Streams\CallbackWrapper; 48use Icewind\Streams\IteratorDirectory; 49use OC\Cache\CappedMemoryCache; 50use OC\Files\Filesystem; 51use OCA\Files_External\Lib\Cache\SmbCacheWrapper; 52use OCP\Files\Storage\StorageAdapter; 53use OCP\Files\StorageNotAvailableException; 54use OCP\Util; 55 56class SMB extends StorageAdapter { 57 /** @var bool */ 58 protected $logActive; 59 60 /** 61 * @var IServer 62 */ 63 protected $server; 64 65 /** 66 * @var IShare 67 */ 68 protected $share; 69 70 /** 71 * @var string 72 */ 73 protected $root; 74 75 /** 76 * @var CappedMemoryCache 77 */ 78 protected $statCache; 79 80 public function __construct($params) { 81 // log switch might be set already (from a subclass), so don't change it. 82 if (!isset($this->logActive)) { 83 $this->logActive = \OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === true; 84 } 85 86 $loggedParams = $params; 87 // remove password from log if it is set 88 if (!empty($loggedParams['password'])) { 89 $loggedParams['password'] = '***removed***'; 90 } 91 $this->log('enter: '.__FUNCTION__.'('.\json_encode($loggedParams).')'); 92 93 if (isset($params['host'], $params['user'], $params['password'], $params['share'])) { 94 $domain = $params['domain'] ?? ''; 95 96 $auth = new BasicAuth($params['user'], $domain, $params['password']); 97 $serverFactory = new ServerFactory(); 98 $this->server = $serverFactory->createServer($params['host'], $auth); 99 $this->share = $this->server->getShare(\trim($params['share'], '/')); 100 101 $shareClass = \get_class($this->share); 102 $this->log("using $shareClass for the connection"); 103 104 $this->root = isset($params['root']) ? $params['root'] : '/'; 105 if (!$this->root || $this->root[0] != '/') { 106 $this->root = '/' . $this->root; 107 } 108 if (\substr($this->root, -1, 1) !== '/') { 109 $this->root .= '/'; 110 } 111 } else { 112 $ex = new \Exception('Invalid configuration: '.\json_encode($loggedParams)); 113 $this->leave(__FUNCTION__, $ex); 114 throw $ex; 115 } 116 $this->statCache = new CappedMemoryCache(); 117 $this->log('leave: '.__FUNCTION__.', getId:'.$this->getId()); 118 } 119 120 public function getId(): string { 121 // FIXME: double slash to keep compatible with the old storage ids, 122 // failure to do so will lead to creation of a new storage id and 123 // loss of shares from the storage 124 return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root; 125 } 126 127 /** 128 * @param string $path 129 * @return string 130 */ 131 protected function buildPath($path) { 132 $this->log('enter: '.__FUNCTION__."($path)"); 133 $result = Filesystem::normalizePath($this->root . '/' . $path, true, false, true); 134 return $this->leave(__FUNCTION__, $result); 135 } 136 137 /** 138 * @param string $path 139 * @return \Icewind\SMB\IFileInfo 140 * @throws StorageNotAvailableException 141 * @throws ForbiddenException 142 * @throws NotFoundException 143 */ 144 protected function getFileInfo($path) { 145 $this->log('enter: '.__FUNCTION__."($path)"); 146 $path = $this->buildPath($path); 147 if (!isset($this->statCache[$path])) { 148 try { 149 $this->log("stat fetching '$path'"); 150 try { 151 $this->statCache[$path] = $this->share->stat($path); 152 } catch (NotFoundException $e) { 153 if ($this->share instanceof IShare) { 154 // smbclient may have problems with the allinfo cmd 155 $this->log("stat for '$path' failed, trying to read parent dir"); 156 $infos = $this->share->dir(\dirname($path)); 157 foreach ($infos as $fileInfo) { 158 if ($fileInfo->getName() === \basename($path)) { 159 $this->statCache[$path] = $fileInfo; 160 break; 161 } 162 } 163 if (empty($this->statCache[$path])) { 164 $this->leave(__FUNCTION__, $e); 165 throw $e; 166 } 167 } else { 168 // trust the results of libsmb 169 $this->leave(__FUNCTION__, $e); 170 throw $e; 171 } 172 } 173 if ($this->isRootDir($path) && $this->statCache[$path]->isHidden()) { 174 $this->log("unhiding stat for '$path'"); 175 // make root never hidden, may happen when accessing a shared drive (mode is 22, archived and readonly - neither is true ... whatever) 176 if ($this->statCache[$path]->isReadOnly()) { 177 $mode = IFileInfo::MODE_DIRECTORY & IFileInfo::MODE_READONLY; 178 } else { 179 $mode = IFileInfo::MODE_DIRECTORY; 180 } 181 $this->statCache[$path] = new FileInfo( 182 $path, 183 '', 184 0, 185 $this->statCache[$path]->getMTime(), 186 $mode, 187 function () { 188 return []; 189 } 190 ); 191 } 192 } catch (ConnectException $e) { 193 $ex = new StorageNotAvailableException( 194 $e->getMessage(), 195 $e->getCode(), 196 $e 197 ); 198 $this->leave(__FUNCTION__, $ex); 199 throw $ex; 200 } catch (ForbiddenException $e) { 201 if ($this->remoteIsShare() && $this->isRootDir($path)) { //mtime may not work for share root 202 $this->log("faking stat for forbidden '$path'"); 203 $this->statCache[$path] = new FileInfo( 204 $path, 205 '', 206 0, 207 $this->shareMTime(), 208 IFileInfo::MODE_DIRECTORY, 209 function () { 210 return []; 211 } 212 ); 213 } else { 214 $this->leave(__FUNCTION__, $e); 215 throw $e; 216 } 217 } 218 } else { 219 $this->log("stat cache hit for '$path'"); 220 } 221 $result = $this->statCache[$path]; 222 return $this->leave(__FUNCTION__, $result); 223 } 224 225 /** 226 * @param string $path 227 * @return \Icewind\SMB\IFileInfo[] 228 * @throws StorageNotAvailableException 229 */ 230 protected function getFolderContents($path) { 231 $this->log('enter: '.__FUNCTION__."($path)"); 232 try { 233 $path = $this->buildPath($path); 234 $result = []; 235 $children = $this->share->dir($path); 236 $trimmedPath = \rtrim($path, '/'); 237 foreach ($children as $fileInfo) { 238 $fullPath = "{$trimmedPath}/{$fileInfo->getName()}"; 239 if (isset($this->statCache[$fullPath])) { 240 // reference in the cache might have its fileinfo's mode 241 // already resolved, so use that 242 $fileInfo = $this->statCache[$fullPath]; 243 } 244 // check if the file is readable before adding it to the list 245 // can't use "isReadable" function here, use smb internals instead 246 try { 247 if ($fileInfo->isHidden()) { 248 $this->log("{$fileInfo->getName()} isn't readable, skipping", Util::DEBUG); 249 } else { 250 $result[] = $fileInfo; 251 //remember entry so we can answer file_exists and filetype without a full stat 252 $this->statCache[$fullPath] = $fileInfo; 253 } 254 } catch (NotFoundException $e) { 255 $this->swallow(__FUNCTION__, $e); 256 } catch (ForbiddenException $e) { 257 $this->swallow(__FUNCTION__, $e); 258 } 259 } 260 } catch (ConnectException $e) { 261 $ex = new StorageNotAvailableException( 262 $e->getMessage(), 263 $e->getCode(), 264 $e 265 ); 266 $this->leave(__FUNCTION__, $ex); 267 throw $ex; 268 } 269 return $this->leave(__FUNCTION__, $result); 270 } 271 272 /** 273 * @param \Icewind\SMB\IFileInfo $info 274 * @return array 275 */ 276 protected function formatInfo($info) { 277 $result = [ 278 'size' => $info->getSize(), 279 'mtime' => $info->getMTime(), 280 ]; 281 if ($info->isDirectory()) { 282 $result['type'] = 'dir'; 283 } else { 284 $result['type'] = 'file'; 285 } 286 return $result; 287 } 288 289 /** 290 * Rename the files. If the source or the target is the root, the rename won't happen. 291 * 292 * @param string $source the old name of the path 293 * @param string $target the new name of the path 294 * @return bool true if the rename is successful, false otherwise 295 */ 296 public function rename($source, $target) { 297 $this->log("enter: rename('$source', '$target')", Util::DEBUG); 298 299 if ($this->isRootDir($source) || $this->isRootDir($target)) { 300 $this->log("refusing to rename \"$source\" to \"$target\""); 301 return $this->leave(__FUNCTION__, false); 302 } 303 304 $buildSource = $this->buildPath($source); 305 $buildTarget = $this->buildPath($target); 306 try { 307 $result = $this->share->rename($buildSource, $buildTarget); 308 if ($result) { 309 $this->removeFromCache($buildSource); 310 $this->removeFromCache($buildTarget); 311 } 312 } catch (AlreadyExistsException $e) { 313 $this->swallow(__FUNCTION__, $e); 314 if ($this->unlink($target)) { 315 $result = $this->share->rename($buildSource, $buildTarget); 316 if ($result) { 317 $this->removeFromCache($buildSource); 318 $this->removeFromCache($buildTarget); 319 } 320 } else { 321 $result = false; 322 } 323 } catch (ConnectException $e) { 324 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 325 $this->leave(__FUNCTION__, $ex); 326 throw $ex; 327 } catch (Exception $e) { 328 $this->swallow(__FUNCTION__, $e); 329 // Icewind\SMB\Exception\Exception, not a plain exception 330 if ($e->getCode() === 22) { 331 // some servers seem to return an error code 22 instead of the expected AlreadyExistException 332 if ($this->unlink($target)) { 333 $result = $this->share->rename($buildSource, $buildTarget); 334 if ($result) { 335 $this->removeFromCache($buildSource); 336 $this->removeFromCache($buildTarget); 337 } 338 } else { 339 $result = false; 340 } 341 } else { 342 $result = false; 343 } 344 } catch (\Exception $e) { 345 $this->swallow(__FUNCTION__, $e); 346 $result = false; 347 } 348 return $this->leave(__FUNCTION__, $result); 349 } 350 351 private function removeFromCache($path) { 352 // TODO The CappedCache does not really clear by prefix. It just clears all. 353 '@phan-var \OC\Cache\CappedMemoryCache $this->statCache'; 354 $this->statCache->clear("$path/"); 355 unset($this->statCache[$path]); 356 } 357 /** 358 * @param string $path 359 * @return array 360 */ 361 public function stat($path) { 362 $this->log('enter: '.__FUNCTION__."($path)"); 363 try { 364 $result = $this->formatInfo($this->getFileInfo($path)); 365 } catch (ConnectException $e) { 366 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 367 $this->leave(__FUNCTION__, $ex); 368 throw $ex; 369 } catch (Exception $e) { 370 $this->swallow(__FUNCTION__, $e); 371 $result = false; 372 } 373 return $this->leave(__FUNCTION__, $result); 374 } 375 376 /** 377 * get the best guess for the modification time of the share 378 * NOTE: modification times do not bubble up the directory tree, basically 379 * we are just guessing a time 380 * 381 * @return int the calculated mtime for the folder 382 */ 383 private function shareMTime() { 384 $this->log('enter: '.__FUNCTION__, Util::DEBUG); 385 $files = $this->share->dir($this->root); 386 $result = 0; 387 foreach ($files as $fileInfo) { 388 if ($fileInfo->getMTime() > $result) { 389 $result = $fileInfo->getMTime(); 390 } 391 } 392 return $this->leave(__FUNCTION__, $result); 393 } 394 /** 395 * Check if the path is our root dir (not the smb one) 396 * 397 * @param string $path the path 398 * @return bool true if it's root, false if not 399 */ 400 private function isRootDir($path) { 401 $this->log('enter: '.__FUNCTION__."($path)", Util::DEBUG); 402 $result = $path === '' || $path === '/' || $path === '.'; 403 return $this->leave(__FUNCTION__, $result); 404 } 405 /** 406 * Check if our root points to a smb share 407 * 408 * @return bool true if our root points to a share false otherwise 409 */ 410 private function remoteIsShare() { 411 $this->log('enter: '.__FUNCTION__, Util::DEBUG); 412 $result = $this->share->getName() && (!$this->root || $this->root === '/'); 413 return $this->leave(__FUNCTION__, $result); 414 } 415 /** 416 * @param string $path 417 * @return bool 418 * @throws StorageNotAvailableException 419 */ 420 public function unlink($path) { 421 $this->log('enter: '.__FUNCTION__."($path)"); 422 423 if ($this->isRootDir($path)) { 424 $this->log("refusing to unlink \"$path\""); 425 return $this->leave(__FUNCTION__, false); 426 } 427 428 $result = false; 429 try { 430 if ($this->is_dir($path)) { 431 $result = $this->rmdir($path); 432 } else { 433 $buildPath = $this->buildPath($path); 434 $this->share->del($buildPath); 435 unset($this->statCache[$buildPath]); 436 $result = true; 437 } 438 } catch (ConnectException $e) { 439 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 440 $this->leave(__FUNCTION__, $ex); 441 throw $ex; 442 } catch (Exception $e) { 443 $this->swallow(__FUNCTION__, $e); 444 } 445 return $this->leave(__FUNCTION__, $result); 446 } 447 448 /** 449 * check if a file or folder has been updated since $time 450 * 451 * @param string $path 452 * @param int $time 453 * @return bool 454 */ 455 public function hasUpdated($path, $time) { 456 $this->log('enter: '.__FUNCTION__."($path, $time)"); 457 $actualTime = $this->filemtime($path); 458 $result = $actualTime > $time; 459 return $this->leave(__FUNCTION__, $result); 460 } 461 462 /** 463 * @param string $path 464 * @param string $mode 465 * @return resource 466 * @throws StorageNotAvailableException 467 */ 468 public function fopen($path, $mode) { 469 $this->log('enter: '.__FUNCTION__."($path, $mode)"); 470 $fullPath = $this->buildPath($path); 471 $result = false; 472 try { 473 switch ($mode) { 474 case 'r': 475 case 'rb': 476 if ($this->file_exists($path)) { 477 $result = $this->share->read($fullPath); 478 } 479 break; 480 case 'w': 481 case 'wb': 482 $source = $this->share->write($fullPath); 483 $result = CallBackWrapper::wrap($source, null, null, function () use ($fullPath) { 484 unset($this->statCache[$fullPath]); 485 }); 486 break; 487 case 'a': 488 case 'ab': 489 case 'r+': 490 case 'w+': 491 case 'wb+': 492 case 'a+': 493 case 'x': 494 case 'x+': 495 case 'c': 496 case 'c+': 497 //emulate these 498 if (\strrpos($path, '.') !== false) { 499 $ext = \substr($path, \strrpos($path, '.')); 500 } else { 501 $ext = ''; 502 } 503 if ($this->file_exists($path)) { 504 if (!$this->isUpdatable($path)) { 505 break; 506 } 507 $tmpFile = $this->getCachedFile($path); 508 } else { 509 if (!$this->isCreatable(\dirname($path))) { 510 break; 511 } 512 $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); 513 } 514 $source = \fopen($tmpFile, $mode); 515 $share = $this->share; 516 $result = CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { 517 unset($this->statCache[$fullPath]); 518 $share->put($tmpFile, $fullPath); 519 \unlink($tmpFile); 520 }); 521 } 522 } catch (ConnectException $e) { 523 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 524 $this->leave(__FUNCTION__, $ex); 525 throw $ex; 526 } catch (Exception $e) { 527 $this->swallow(__FUNCTION__, $e); 528 } 529 return $this->leave(__FUNCTION__, $result); 530 } 531 532 public function rmdir($path) { 533 $this->log('enter: '.__FUNCTION__."($path)"); 534 535 if ($this->isRootDir($path)) { 536 $this->log("refusing to delete \"$path\""); 537 return $this->leave(__FUNCTION__, false); 538 } 539 540 $result = false; 541 try { 542 $buildPath = $this->buildPath($path); 543 $content = $this->share->dir($buildPath); 544 foreach ($content as $file) { 545 if ($file->isDirectory()) { 546 $this->rmdir($path . '/' . $file->getName()); 547 } else { 548 $this->share->del($file->getPath()); 549 } 550 } 551 $this->share->rmdir($buildPath); 552 $this->removeFromCache($buildPath); 553 $result = true; 554 } catch (ConnectException $e) { 555 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 556 $this->leave(__FUNCTION__, $ex); 557 throw $ex; 558 } catch (Exception $e) { 559 $this->swallow(__FUNCTION__, $e); 560 } 561 return $this->leave(__FUNCTION__, $result); 562 } 563 564 public function touch($path, $time = null) { 565 $this->log('enter: '.__FUNCTION__."($path, $time)"); 566 $result = false; 567 try { 568 if (!$this->file_exists($path)) { 569 $fh = $this->share->write($this->buildPath($path)); 570 \fclose($fh); 571 $result = true; 572 } 573 } catch (ConnectException $e) { 574 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 575 $this->leave(__FUNCTION__, $ex); 576 throw $ex; 577 } catch (Exception $e) { 578 $this->swallow(__FUNCTION__, $e); 579 } 580 return $this->leave(__FUNCTION__, $result); 581 } 582 583 public function opendir($path) { 584 $this->log('enter: '.__FUNCTION__."($path)"); 585 $result = false; 586 try { 587 $files = $this->getFolderContents($path); 588 $names = \array_map(function ($info) { 589 /** @var \Icewind\SMB\IFileInfo $info */ 590 return $info->getName(); 591 }, $files); 592 $result = IteratorDirectory::wrap($names); 593 } catch (ConnectException $e) { 594 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 595 $this->leave(__FUNCTION__, $ex); 596 throw $ex; 597 } catch (Exception $e) { 598 $this->swallow(__FUNCTION__, $e); 599 } 600 return $this->leave(__FUNCTION__, $result); 601 } 602 603 public function filetype($path) { 604 $this->log('enter: '.__FUNCTION__."($path)"); 605 $result = false; 606 try { 607 $result = $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file'; 608 } catch (ConnectException $e) { 609 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 610 $this->leave(__FUNCTION__, $ex); 611 throw $ex; 612 } catch (Exception $e) { 613 $this->swallow(__FUNCTION__, $e); 614 } 615 return $this->leave(__FUNCTION__, $result); 616 } 617 618 public function mkdir($path) { 619 $this->log('enter: '.__FUNCTION__."($path)"); 620 $result = false; 621 $path = $this->buildPath($path); 622 try { 623 $result = $this->share->mkdir($path); 624 } catch (ConnectException $e) { 625 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 626 $this->leave(__FUNCTION__, $ex); 627 throw $ex; 628 } catch (Exception $e) { 629 $this->swallow(__FUNCTION__, $e); 630 } 631 return $this->leave(__FUNCTION__, $result); 632 } 633 634 public function file_exists($path) { 635 $this->log('enter: '.__FUNCTION__."($path)"); 636 $result = false; 637 try { 638 $this->getFileInfo($path); 639 $result = true; 640 } catch (ConnectException $e) { 641 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 642 $this->leave(__FUNCTION__, $ex); 643 throw $ex; 644 } catch (Exception $e) { 645 $this->swallow(__FUNCTION__, $e); 646 } 647 return $this->leave(__FUNCTION__, $result); 648 } 649 650 public function isReadable($path) { 651 $this->log('enter: '.__FUNCTION__."($path)"); 652 if ($this->isRootDir($path)) { 653 return $this->leave(__FUNCTION__, true); 654 } 655 656 $result = false; 657 try { 658 $info = $this->getFileInfo($path); 659 $result = !$info->isHidden(); 660 } catch (ConnectException $e) { 661 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 662 $this->leave(__FUNCTION__, $ex); 663 throw $ex; 664 } catch (Exception $e) { 665 $this->swallow(__FUNCTION__, $e); 666 } 667 return $this->leave(__FUNCTION__, $result); 668 } 669 670 public function isCreatable($path) { 671 $this->log('enter: '.__FUNCTION__."($path)"); 672 if ($this->isRootDir($path)) { 673 return $this->leave(__FUNCTION__, true); 674 } 675 return $this->leave(__FUNCTION__, parent::isCreatable($path)); 676 } 677 678 public function isUpdatable($path) { 679 $this->log('enter: '.__FUNCTION__."($path)"); 680 if ($this->isRootDir($path)) { 681 // root path mustn't be changed 682 return $this->leave(__FUNCTION__, false); 683 } 684 685 $result = false; 686 try { 687 $info = $this->getFileInfo($path); 688 // following windows behaviour for read-only folders: they can be written into 689 // (https://support.microsoft.com/en-us/kb/326549 - "cause" section) 690 $result = !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path)); 691 } catch (ConnectException $e) { 692 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 693 $this->leave(__FUNCTION__, $ex); 694 throw $ex; 695 } catch (Exception $e) { 696 $this->swallow(__FUNCTION__, $e); 697 } 698 return $this->leave(__FUNCTION__, $result); 699 } 700 701 public function isDeletable($path) { 702 $this->log('enter: '.__FUNCTION__."($path)"); 703 if ($this->isRootDir($path)) { 704 // root path mustn't be deleted 705 return $this->leave(__FUNCTION__, false); 706 } 707 708 $result = false; 709 try { 710 $info = $this->getFileInfo($path); 711 $result = !$info->isHidden() && !$info->isReadOnly(); 712 } catch (ConnectException $e) { 713 $ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); 714 $this->leave(__FUNCTION__, $ex); 715 throw $ex; 716 } catch (Exception $e) { 717 $this->swallow(__FUNCTION__, $e); 718 } 719 return $this->leave(__FUNCTION__, $result); 720 } 721 722 /** 723 * check if smbclient is installed 724 */ 725 public static function checkDependencies() { 726 return ( 727 (bool)\OC_Helper::findBinaryPath('smbclient') 728 || NativeServer::available(new System()) 729 ) ? true : ['smbclient']; 730 } 731 732 /** 733 * Test a storage for availability 734 * 735 * @return bool 736 */ 737 public function test() { 738 $this->log('enter: '.__FUNCTION__."()"); 739 $result = false; 740 try { 741 $result = parent::test(); 742 } catch (Exception $e) { 743 $this->swallow(__FUNCTION__, $e); 744 } 745 return $this->leave(__FUNCTION__, $result); 746 } 747 748 /** 749 * @param string $message 750 * @param int $level 751 * @param string $from 752 */ 753 private function log($message, $level = Util::DEBUG, $from = 'smb') { 754 if ($this->logActive) { 755 Util::writeLog($from, $message, $level); 756 } 757 } 758 759 /** 760 * if smb.logging.enable is set to true in the config will log a leave line 761 * with the given function, the return value or the exception 762 * 763 * @param $function 764 * @param mixed $result an exception will be logged and then returned 765 * @return mixed 766 */ 767 private function leave($function, $result) { 768 if (!$this->logActive) { 769 //don't bother building log strings 770 return $result; 771 } elseif ($result === true) { 772 Util::writeLog('smb', "leave: $function, return true", Util::DEBUG); 773 } elseif ($result === false) { 774 Util::writeLog('smb', "leave: $function, return false", Util::DEBUG); 775 } elseif (\is_string($result)) { 776 Util::writeLog('smb', "leave: $function, return '$result'", Util::DEBUG); 777 } elseif (\is_resource($result)) { 778 Util::writeLog('smb', "leave: $function, return resource", Util::DEBUG); 779 } elseif ($result instanceof \Exception) { 780 Util::writeLog('smb', "leave: $function, throw ".\get_class($result) 781 .' - code: '.$result->getCode() 782 .' message: '.$result->getMessage() 783 .' trace: '.$result->getTraceAsString(), Util::DEBUG); 784 } else { 785 Util::writeLog('smb', "leave: $function, return ".\json_encode($result, true), Util::DEBUG); 786 } 787 return $result; 788 } 789 790 private function swallow($function, \Exception $exception) { 791 if ($this->logActive) { 792 Util::writeLog('smb', "$function swallowing ".\get_class($exception) 793 .' - code: '.$exception->getCode() 794 .' message: '.$exception->getMessage() 795 .' trace: '.$exception->getTraceAsString(), Util::DEBUG); 796 } 797 } 798 799 /** 800 * immediately close / free connection 801 */ 802 public function __destruct() { 803 unset($this->share); 804 } 805} 806