filesystem.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. /**
  9. * Class for abstraction of filesystem functions
  10. * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object
  11. * this class should also handle all the file permission related stuff
  12. *
  13. * Hooks provided:
  14. * read(path)
  15. * write(path, &run)
  16. * post_write(path)
  17. * create(path, &run) (when a file is created, both create and write will be emitted in that order)
  18. * post_create(path)
  19. * delete(path, &run)
  20. * post_delete(path)
  21. * rename(oldpath,newpath, &run)
  22. * post_rename(oldpath,newpath)
  23. * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order)
  24. * post_rename(oldpath,newpath)
  25. * post_initMountPoints(user, user_dir)
  26. *
  27. * the &run parameter can be set to false to prevent the operation from occurring
  28. */
  29. namespace OC\Files;
  30. use OC\Files\Storage\Loader;
  31. const SPACE_NOT_COMPUTED = -1;
  32. const SPACE_UNKNOWN = -2;
  33. const SPACE_UNLIMITED = -3;
  34. class Filesystem {
  35. /**
  36. * @var Mount\Manager $mounts
  37. */
  38. private static $mounts;
  39. public static $loaded = false;
  40. /**
  41. * @var \OC\Files\View $defaultInstance
  42. */
  43. static private $defaultInstance;
  44. /**
  45. * classname which used for hooks handling
  46. * used as signalclass in OC_Hooks::emit()
  47. */
  48. const CLASSNAME = 'OC_Filesystem';
  49. /**
  50. * signalname emitted before file renaming
  51. *
  52. * @param string $oldpath
  53. * @param string $newpath
  54. */
  55. const signal_rename = 'rename';
  56. /**
  57. * signal emitted after file renaming
  58. *
  59. * @param string $oldpath
  60. * @param string $newpath
  61. */
  62. const signal_post_rename = 'post_rename';
  63. /**
  64. * signal emitted before file/dir creation
  65. *
  66. * @param string $path
  67. * @param bool $run changing this flag to false in hook handler will cancel event
  68. */
  69. const signal_create = 'create';
  70. /**
  71. * signal emitted after file/dir creation
  72. *
  73. * @param string $path
  74. * @param bool $run changing this flag to false in hook handler will cancel event
  75. */
  76. const signal_post_create = 'post_create';
  77. /**
  78. * signal emits before file/dir copy
  79. *
  80. * @param string $oldpath
  81. * @param string $newpath
  82. * @param bool $run changing this flag to false in hook handler will cancel event
  83. */
  84. const signal_copy = 'copy';
  85. /**
  86. * signal emits after file/dir copy
  87. *
  88. * @param string $oldpath
  89. * @param string $newpath
  90. */
  91. const signal_post_copy = 'post_copy';
  92. /**
  93. * signal emits before file/dir save
  94. *
  95. * @param string $path
  96. * @param bool $run changing this flag to false in hook handler will cancel event
  97. */
  98. const signal_write = 'write';
  99. /**
  100. * signal emits after file/dir save
  101. *
  102. * @param string $path
  103. */
  104. const signal_post_write = 'post_write';
  105. /**
  106. * signal emits when reading file/dir
  107. *
  108. * @param string $path
  109. */
  110. const signal_read = 'read';
  111. /**
  112. * signal emits when removing file/dir
  113. *
  114. * @param string $path
  115. */
  116. const signal_delete = 'delete';
  117. /**
  118. * parameters definitions for signals
  119. */
  120. const signal_param_path = 'path';
  121. const signal_param_oldpath = 'oldpath';
  122. const signal_param_newpath = 'newpath';
  123. /**
  124. * run - changing this flag to false in hook handler will cancel event
  125. */
  126. const signal_param_run = 'run';
  127. /**
  128. * @var \OC\Files\Storage\Loader $loader
  129. */
  130. private static $loader;
  131. /**
  132. * @param callable $wrapper
  133. */
  134. public static function addStorageWrapper($wrapper) {
  135. self::getLoader()->addStorageWrapper($wrapper);
  136. $mounts = self::getMountManager()->getAll();
  137. foreach ($mounts as $mount) {
  138. $mount->wrapStorage($wrapper);
  139. }
  140. }
  141. public static function getLoader() {
  142. if (!self::$loader) {
  143. self::$loader = new Loader();
  144. }
  145. return self::$loader;
  146. }
  147. public static function getMountManager() {
  148. if (!self::$mounts) {
  149. \OC_Util::setupFS();
  150. }
  151. return self::$mounts;
  152. }
  153. /**
  154. * get the mountpoint of the storage object for a path
  155. * ( note: because a storage is not always mounted inside the fakeroot, the
  156. * returned mountpoint is relative to the absolute root of the filesystem
  157. * and doesn't take the chroot into account )
  158. *
  159. * @param string $path
  160. * @return string
  161. */
  162. static public function getMountPoint($path) {
  163. if (!self::$mounts) {
  164. \OC_Util::setupFS();
  165. }
  166. $mount = self::$mounts->find($path);
  167. if ($mount) {
  168. return $mount->getMountPoint();
  169. } else {
  170. return '';
  171. }
  172. }
  173. /**
  174. * get a list of all mount points in a directory
  175. *
  176. * @param string $path
  177. * @return string[]
  178. */
  179. static public function getMountPoints($path) {
  180. if (!self::$mounts) {
  181. \OC_Util::setupFS();
  182. }
  183. $result = array();
  184. $mounts = self::$mounts->findIn($path);
  185. foreach ($mounts as $mount) {
  186. $result[] = $mount->getMountPoint();
  187. }
  188. return $result;
  189. }
  190. /**
  191. * get the storage mounted at $mountPoint
  192. *
  193. * @param string $mountPoint
  194. * @return \OC\Files\Storage\Storage
  195. */
  196. public static function getStorage($mountPoint) {
  197. if (!self::$mounts) {
  198. \OC_Util::setupFS();
  199. }
  200. $mount = self::$mounts->find($mountPoint);
  201. return $mount->getStorage();
  202. }
  203. /**
  204. * @param $id
  205. * @return Mount\Mount[]
  206. */
  207. public static function getMountByStorageId($id) {
  208. if (!self::$mounts) {
  209. \OC_Util::setupFS();
  210. }
  211. return self::$mounts->findByStorageId($id);
  212. }
  213. /**
  214. * @param $id
  215. * @return Mount\Mount[]
  216. */
  217. public static function getMountByNumericId($id) {
  218. if (!self::$mounts) {
  219. \OC_Util::setupFS();
  220. }
  221. return self::$mounts->findByNumericId($id);
  222. }
  223. /**
  224. * resolve a path to a storage and internal path
  225. *
  226. * @param string $path
  227. * @return array consisting of the storage and the internal path
  228. */
  229. static public function resolvePath($path) {
  230. if (!self::$mounts) {
  231. \OC_Util::setupFS();
  232. }
  233. $mount = self::$mounts->find($path);
  234. if ($mount) {
  235. return array($mount->getStorage(), $mount->getInternalPath($path));
  236. } else {
  237. return array(null, null);
  238. }
  239. }
  240. static public function init($user, $root) {
  241. if (self::$defaultInstance) {
  242. return false;
  243. }
  244. self::getLoader();
  245. self::$defaultInstance = new View($root);
  246. if (!self::$mounts) {
  247. self::$mounts = new Mount\Manager();
  248. }
  249. //load custom mount config
  250. self::initMountPoints($user);
  251. self::$loaded = true;
  252. return true;
  253. }
  254. static public function initMounts() {
  255. if (!self::$mounts) {
  256. self::$mounts = new Mount\Manager();
  257. }
  258. }
  259. /**
  260. * Initialize system and personal mount points for a user
  261. *
  262. * @param string $user
  263. */
  264. public static function initMountPoints($user = '') {
  265. if ($user == '') {
  266. $user = \OC_User::getUser();
  267. }
  268. $parser = new \OC\ArrayParser();
  269. $root = \OC_User::getHome($user);
  270. $userObject = \OC_User::getManager()->get($user);
  271. if (!is_null($userObject)) {
  272. // check for legacy home id (<= 5.0.12)
  273. if (\OC\Files\Cache\Storage::exists('local::' . $root . '/')) {
  274. self::mount('\OC\Files\Storage\Home', array('user' => $userObject, 'legacy' => true), $user);
  275. }
  276. else {
  277. self::mount('\OC\Files\Storage\Home', array('user' => $userObject), $user);
  278. }
  279. }
  280. else {
  281. self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user);
  282. }
  283. $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data");
  284. //move config file to it's new position
  285. if (is_file(\OC::$SERVERROOT . '/config/mount.json')) {
  286. rename(\OC::$SERVERROOT . '/config/mount.json', $datadir . '/mount.json');
  287. }
  288. // Load system mount points
  289. if (is_file(\OC::$SERVERROOT . '/config/mount.php') or is_file($datadir . '/mount.json')) {
  290. if (is_file($datadir . '/mount.json')) {
  291. $mountConfig = json_decode(file_get_contents($datadir . '/mount.json'), true);
  292. } elseif (is_file(\OC::$SERVERROOT . '/config/mount.php')) {
  293. $mountConfig = $parser->parsePHP(file_get_contents(\OC::$SERVERROOT . '/config/mount.php'));
  294. }
  295. if (isset($mountConfig['global'])) {
  296. foreach ($mountConfig['global'] as $mountPoint => $options) {
  297. self::mount($options['class'], $options['options'], $mountPoint);
  298. }
  299. }
  300. if (isset($mountConfig['group'])) {
  301. foreach ($mountConfig['group'] as $group => $mounts) {
  302. if (\OC_Group::inGroup($user, $group)) {
  303. foreach ($mounts as $mountPoint => $options) {
  304. $mountPoint = self::setUserVars($user, $mountPoint);
  305. foreach ($options as &$option) {
  306. $option = self::setUserVars($user, $option);
  307. }
  308. self::mount($options['class'], $options['options'], $mountPoint);
  309. }
  310. }
  311. }
  312. }
  313. if (isset($mountConfig['user'])) {
  314. foreach ($mountConfig['user'] as $mountUser => $mounts) {
  315. if ($mountUser === 'all' or strtolower($mountUser) === strtolower($user)) {
  316. foreach ($mounts as $mountPoint => $options) {
  317. $mountPoint = self::setUserVars($user, $mountPoint);
  318. foreach ($options as &$option) {
  319. $option = self::setUserVars($user, $option);
  320. }
  321. self::mount($options['class'], $options['options'], $mountPoint);
  322. }
  323. }
  324. }
  325. }
  326. }
  327. // Load personal mount points
  328. if (is_file($root . '/mount.php') or is_file($root . '/mount.json')) {
  329. if (is_file($root . '/mount.json')) {
  330. $mountConfig = json_decode(file_get_contents($root . '/mount.json'), true);
  331. } elseif (is_file($root . '/mount.php')) {
  332. $mountConfig = $parser->parsePHP(file_get_contents($root . '/mount.php'));
  333. }
  334. if (isset($mountConfig['user'][$user])) {
  335. foreach ($mountConfig['user'][$user] as $mountPoint => $options) {
  336. self::mount($options['class'], $options['options'], $mountPoint);
  337. }
  338. }
  339. }
  340. // Chance to mount for other storages
  341. \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user, 'user_dir' => $root));
  342. }
  343. /**
  344. * fill in the correct values for $user
  345. *
  346. * @param string $user
  347. * @param string $input
  348. * @return string
  349. */
  350. private static function setUserVars($user, $input) {
  351. return str_replace('$user', $user, $input);
  352. }
  353. /**
  354. * get the default filesystem view
  355. *
  356. * @return View
  357. */
  358. static public function getView() {
  359. return self::$defaultInstance;
  360. }
  361. /**
  362. * tear down the filesystem, removing all storage providers
  363. */
  364. static public function tearDown() {
  365. self::clearMounts();
  366. self::$defaultInstance = null;
  367. }
  368. /**
  369. * @brief get the relative path of the root data directory for the current user
  370. * @return string
  371. *
  372. * Returns path like /admin/files
  373. */
  374. static public function getRoot() {
  375. return self::$defaultInstance->getRoot();
  376. }
  377. /**
  378. * clear all mounts and storage backends
  379. */
  380. public static function clearMounts() {
  381. if (self::$mounts) {
  382. self::$mounts->clear();
  383. }
  384. }
  385. /**
  386. * mount an \OC\Files\Storage\Storage in our virtual filesystem
  387. *
  388. * @param \OC\Files\Storage\Storage|string $class
  389. * @param array $arguments
  390. * @param string $mountpoint
  391. */
  392. static public function mount($class, $arguments, $mountpoint) {
  393. if (!self::$mounts) {
  394. \OC_Util::setupFS();
  395. }
  396. $mount = new Mount\Mount($class, $mountpoint, $arguments, self::getLoader());
  397. self::$mounts->addMount($mount);
  398. }
  399. /**
  400. * return the path to a local version of the file
  401. * we need this because we can't know if a file is stored local or not from
  402. * outside the filestorage and for some purposes a local file is needed
  403. *
  404. * @param string $path
  405. * @return string
  406. */
  407. static public function getLocalFile($path) {
  408. return self::$defaultInstance->getLocalFile($path);
  409. }
  410. /**
  411. * @param string $path
  412. * @return string
  413. */
  414. static public function getLocalFolder($path) {
  415. return self::$defaultInstance->getLocalFolder($path);
  416. }
  417. /**
  418. * return path to file which reflects one visible in browser
  419. *
  420. * @param string $path
  421. * @return string
  422. */
  423. static public function getLocalPath($path) {
  424. $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
  425. $newpath = $path;
  426. if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
  427. $newpath = substr($path, strlen($datadir));
  428. }
  429. return $newpath;
  430. }
  431. /**
  432. * check if the requested path is valid
  433. *
  434. * @param string $path
  435. * @return bool
  436. */
  437. static public function isValidPath($path) {
  438. $path = self::normalizePath($path);
  439. if (!$path || $path[0] !== '/') {
  440. $path = '/' . $path;
  441. }
  442. if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
  443. return false;
  444. }
  445. return true;
  446. }
  447. /**
  448. * checks if a file is blacklisted for storage in the filesystem
  449. * Listens to write and rename hooks
  450. *
  451. * @param array $data from hook
  452. */
  453. static public function isBlacklisted($data) {
  454. if (isset($data['path'])) {
  455. $path = $data['path'];
  456. } else if (isset($data['newpath'])) {
  457. $path = $data['newpath'];
  458. }
  459. if (isset($path)) {
  460. if (self::isFileBlacklisted($path)) {
  461. $data['run'] = false;
  462. }
  463. }
  464. }
  465. /**
  466. * @param string $filename
  467. * @return bool
  468. */
  469. static public function isFileBlacklisted($filename) {
  470. $blacklist = \OC_Config::getValue('blacklisted_files', array('.htaccess'));
  471. $filename = strtolower(basename($filename));
  472. return (in_array($filename, $blacklist));
  473. }
  474. /**
  475. * @brief check if the directory should be ignored when scanning
  476. * NOTE: the special directories . and .. would cause never ending recursion
  477. * @param String $dir
  478. * @return boolean
  479. */
  480. static public function isIgnoredDir($dir) {
  481. if ($dir === '.' || $dir === '..') {
  482. return true;
  483. }
  484. return false;
  485. }
  486. /**
  487. * following functions are equivalent to their php builtin equivalents for arguments/return values.
  488. */
  489. static public function mkdir($path) {
  490. return self::$defaultInstance->mkdir($path);
  491. }
  492. static public function rmdir($path) {
  493. return self::$defaultInstance->rmdir($path);
  494. }
  495. static public function opendir($path) {
  496. return self::$defaultInstance->opendir($path);
  497. }
  498. static public function readdir($path) {
  499. return self::$defaultInstance->readdir($path);
  500. }
  501. static public function is_dir($path) {
  502. return self::$defaultInstance->is_dir($path);
  503. }
  504. static public function is_file($path) {
  505. return self::$defaultInstance->is_file($path);
  506. }
  507. static public function stat($path) {
  508. return self::$defaultInstance->stat($path);
  509. }
  510. static public function filetype($path) {
  511. return self::$defaultInstance->filetype($path);
  512. }
  513. static public function filesize($path) {
  514. return self::$defaultInstance->filesize($path);
  515. }
  516. static public function readfile($path) {
  517. return self::$defaultInstance->readfile($path);
  518. }
  519. static public function isCreatable($path) {
  520. return self::$defaultInstance->isCreatable($path);
  521. }
  522. static public function isReadable($path) {
  523. return self::$defaultInstance->isReadable($path);
  524. }
  525. static public function isUpdatable($path) {
  526. return self::$defaultInstance->isUpdatable($path);
  527. }
  528. static public function isDeletable($path) {
  529. return self::$defaultInstance->isDeletable($path);
  530. }
  531. static public function isSharable($path) {
  532. return self::$defaultInstance->isSharable($path);
  533. }
  534. static public function file_exists($path) {
  535. return self::$defaultInstance->file_exists($path);
  536. }
  537. static public function filemtime($path) {
  538. return self::$defaultInstance->filemtime($path);
  539. }
  540. static public function touch($path, $mtime = null) {
  541. return self::$defaultInstance->touch($path, $mtime);
  542. }
  543. static public function file_get_contents($path) {
  544. return self::$defaultInstance->file_get_contents($path);
  545. }
  546. static public function file_put_contents($path, $data) {
  547. return self::$defaultInstance->file_put_contents($path, $data);
  548. }
  549. static public function unlink($path) {
  550. return self::$defaultInstance->unlink($path);
  551. }
  552. static public function rename($path1, $path2) {
  553. return self::$defaultInstance->rename($path1, $path2);
  554. }
  555. static public function copy($path1, $path2) {
  556. return self::$defaultInstance->copy($path1, $path2);
  557. }
  558. static public function fopen($path, $mode) {
  559. return self::$defaultInstance->fopen($path, $mode);
  560. }
  561. static public function toTmpFile($path) {
  562. return self::$defaultInstance->toTmpFile($path);
  563. }
  564. static public function fromTmpFile($tmpFile, $path) {
  565. return self::$defaultInstance->fromTmpFile($tmpFile, $path);
  566. }
  567. static public function getMimeType($path) {
  568. return self::$defaultInstance->getMimeType($path);
  569. }
  570. static public function hash($type, $path, $raw = false) {
  571. return self::$defaultInstance->hash($type, $path, $raw);
  572. }
  573. static public function free_space($path = '/') {
  574. return self::$defaultInstance->free_space($path);
  575. }
  576. static public function search($query) {
  577. return self::$defaultInstance->search($query);
  578. }
  579. static public function searchByMime($query) {
  580. return self::$defaultInstance->searchByMime($query);
  581. }
  582. /**
  583. * check if a file or folder has been updated since $time
  584. *
  585. * @param string $path
  586. * @param int $time
  587. * @return bool
  588. */
  589. static public function hasUpdated($path, $time) {
  590. return self::$defaultInstance->hasUpdated($path, $time);
  591. }
  592. /**
  593. * @brief Fix common problems with a file path
  594. * @param string $path
  595. * @param bool $stripTrailingSlash
  596. * @return string
  597. */
  598. public static function normalizePath($path, $stripTrailingSlash = true) {
  599. if ($path == '') {
  600. return '/';
  601. }
  602. //no windows style slashes
  603. $path = str_replace('\\', '/', $path);
  604. //add leading slash
  605. if ($path[0] !== '/') {
  606. $path = '/' . $path;
  607. }
  608. // remove '/./'
  609. // ugly, but str_replace() can't replace them all in one go
  610. // as the replacement itself is part of the search string
  611. // which will only be found during the next iteration
  612. while (strpos($path, '/./') !== false) {
  613. $path = str_replace('/./', '/', $path);
  614. }
  615. // remove sequences of slashes
  616. $path = preg_replace('#/{2,}#', '/', $path);
  617. //remove trailing slash
  618. if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') {
  619. $path = substr($path, 0, -1);
  620. }
  621. // remove trailing '/.'
  622. if (substr($path, -2) == '/.') {
  623. $path = substr($path, 0, -2);
  624. }
  625. //normalize unicode if possible
  626. $path = \OC_Util::normalizeUnicode($path);
  627. return $path;
  628. }
  629. /**
  630. * get the filesystem info
  631. *
  632. * @param string $path
  633. * @param boolean $includeMountPoints whether to add mountpoint sizes,
  634. * defaults to true
  635. * @return array
  636. *
  637. * returns an associative array with the following keys:
  638. * - size
  639. * - mtime
  640. * - mimetype
  641. * - encrypted
  642. * - versioned
  643. */
  644. public static function getFileInfo($path, $includeMountPoints = true) {
  645. return self::$defaultInstance->getFileInfo($path, $includeMountPoints);
  646. }
  647. /**
  648. * change file metadata
  649. *
  650. * @param string $path
  651. * @param array $data
  652. * @return int
  653. *
  654. * returns the fileid of the updated file
  655. */
  656. public static function putFileInfo($path, $data) {
  657. return self::$defaultInstance->putFileInfo($path, $data);
  658. }
  659. /**
  660. * get the content of a directory
  661. *
  662. * @param string $directory path under datadirectory
  663. * @param string $mimetype_filter limit returned content to this mimetype or mimepart
  664. * @return array
  665. */
  666. public static function getDirectoryContent($directory, $mimetype_filter = '') {
  667. return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
  668. }
  669. /**
  670. * Get the path of a file by id
  671. *
  672. * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
  673. *
  674. * @param int $id
  675. * @return string
  676. */
  677. public static function getPath($id) {
  678. return self::$defaultInstance->getPath($id);
  679. }
  680. /**
  681. * Get the owner for a file or folder
  682. *
  683. * @param string $path
  684. * @return string
  685. */
  686. public static function getOwner($path) {
  687. return self::$defaultInstance->getOwner($path);
  688. }
  689. /**
  690. * get the ETag for a file or folder
  691. *
  692. * @param string $path
  693. * @return string
  694. */
  695. static public function getETag($path) {
  696. return self::$defaultInstance->getETag($path);
  697. }
  698. }
  699. \OC_Util::setupFS();