PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/PHPDaemon/FS/FileSystem.php

http://github.com/kakserpom/phpdaemon
PHP | 722 lines | 448 code | 49 blank | 225 comment | 68 complexity | 0f4f83f2054ec945d28c3b03cec369b3 MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. namespace PHPDaemon\FS;
  3. use PHPDaemon\Cache\CappedStorageHits;
  4. use PHPDaemon\Core\CallbackWrapper;
  5. use PHPDaemon\Core\Daemon;
  6. use PHPDaemon\Core\EventLoop;
  7. use PHPDaemon\Traits\EventLoopContainer;
  8. /**
  9. * FileSystem
  10. * @package PHPDaemon\FS
  11. * @author Vasily Zorin <maintainer@daemon.io>
  12. */
  13. class FileSystem
  14. {
  15. use \PHPDaemon\Traits\ClassWatchdog;
  16. use \PHPDaemon\Traits\StaticObjectWatchdog;
  17. use EventLoopContainer;
  18. /**
  19. * @var boolean Is EIO supported?
  20. */
  21. public static $supported;
  22. /**
  23. * @var Event Main FS event
  24. */
  25. public static $ev;
  26. /**
  27. * @var resource EIO file descriptor
  28. */
  29. public static $fd;
  30. /**
  31. * @var array Mode types
  32. */
  33. public static $modeTypes = [
  34. 0140000 => 's',
  35. 0120000 => 'l',
  36. 0100000 => 'f',
  37. 0060000 => 'b',
  38. 0040000 => 'd',
  39. 0020000 => 'c',
  40. 0010000 => 'p',
  41. ];
  42. /**
  43. * @var integer TTL for bad descriptors in seconds
  44. */
  45. public static $badFDttl = 5;
  46. /**
  47. * @var PHPDaemon\Cache\CappedStorage File descriptor cache
  48. */
  49. public static $fdCache;
  50. /**
  51. * @var integer Maximum number of open files in cache
  52. */
  53. public static $fdCacheSize = 128;
  54. /**
  55. * @var string Required EIO version
  56. */
  57. public static $eioVer = '1.2.1';
  58. /**
  59. * Initialize FS driver
  60. * @return void
  61. */
  62. public static function init()
  63. {
  64. if (!Daemon::$config->eioenabled->value) {
  65. self::$supported = false;
  66. return;
  67. }
  68. if (!self::$supported = Daemon::loadModuleIfAbsent('eio', self::$eioVer)) {
  69. Daemon::log('FS: missing pecl-eio >= ' . self::$eioVer . '. Filesystem I/O performance compromised. Consider installing pecl-eio. `pecl install http://pecl.php.net/get/eio`');
  70. return;
  71. }
  72. self::$fdCache = new CappedStorageHits(self::$fdCacheSize);
  73. eio_init();
  74. }
  75. public function __construct()
  76. {
  77. }
  78. /**
  79. * Initialize main FS event
  80. * @return void
  81. */
  82. public static function initEvent()
  83. {
  84. if (!self::$supported) {
  85. return;
  86. }
  87. self::updateConfig();
  88. self::$fd = eio_get_event_stream();
  89. self::$ev = EventLoop::$instance->event(self::$fd,
  90. \Event::READ | \Event::PERSIST,
  91. function ($fd, $events, $arg) {
  92. while (eio_nreqs()) {
  93. eio_poll();
  94. }
  95. }
  96. );
  97. self::$ev->add();
  98. }
  99. /**
  100. * Checks if file exists and readable
  101. * @param string $path Path
  102. * @return boolean Exists and readable?
  103. */
  104. public static function checkFileReadable($path)
  105. {
  106. return is_file($path) && is_readable($path);
  107. }
  108. /**
  109. * Block until all FS events are completed
  110. * @return void
  111. */
  112. public static function waitAllEvents()
  113. {
  114. if (!self::$supported) {
  115. return;
  116. }
  117. while (eio_nreqs()) {
  118. eio_poll();
  119. }
  120. }
  121. /**
  122. * Called when config is updated
  123. * @return void
  124. */
  125. public static function updateConfig()
  126. {
  127. if (Daemon::$config->eiosetmaxidle->value !== null) {
  128. eio_set_max_idle(Daemon::$config->eiosetmaxidle->value);
  129. }
  130. if (Daemon::$config->eiosetmaxparallel->value !== null) {
  131. eio_set_max_parallel(Daemon::$config->eiosetmaxparallel->value);
  132. }
  133. if (Daemon::$config->eiosetmaxpollreqs->value !== null) {
  134. eio_set_max_poll_reqs(Daemon::$config->eiosetmaxpollreqs->value);
  135. }
  136. if (Daemon::$config->eiosetmaxpolltime->value !== null) {
  137. eio_set_max_poll_time(Daemon::$config->eiosetmaxpolltime->value);
  138. }
  139. if (Daemon::$config->eiosetminparallel->value !== null) {
  140. eio_set_min_parallel(Daemon::$config->eiosetminparallel->value);
  141. }
  142. }
  143. /**
  144. * Sanitize path
  145. * @param string $path Path
  146. * @return string Sanitized path
  147. */
  148. public static function sanitizePath($path)
  149. {
  150. return str_replace(["\x00", "../"], '', $path);
  151. }
  152. /**
  153. * Prepare value of stat()
  154. * @param mixed $stat Data
  155. * @return array hash
  156. */
  157. public static function statPrepare($stat)
  158. {
  159. if ($stat === -1 || !$stat) {
  160. return -1;
  161. }
  162. $stat['type'] = FileSystem::$modeTypes[$stat['mode'] & 0170000];
  163. return $stat;
  164. }
  165. /**
  166. * Gets stat() information
  167. * @param string $path Path
  168. * @param callable $cb Callback
  169. * @param integer $pri Priority
  170. * @return resource|true
  171. */
  172. public static function stat($path, $cb, $pri = EIO_PRI_DEFAULT)
  173. {
  174. $cb = CallbackWrapper::forceWrap($cb);
  175. if (!self::$supported) {
  176. $cb($path, FileSystem::statPrepare(@stat($path)));
  177. return true;
  178. }
  179. return eio_stat($path, $pri, function ($path, $stat) use ($cb) {
  180. $cb($path, FileSystem::statPrepare($stat));
  181. }, $path);
  182. }
  183. /**
  184. * Unlink file
  185. * @param string $path Path
  186. * @param callable $cb Callback
  187. * @param integer $pri Priority
  188. * @return resource|boolean
  189. */
  190. public static function unlink($path, $cb = null, $pri = EIO_PRI_DEFAULT)
  191. {
  192. $cb = CallbackWrapper::forceWrap($cb);
  193. if (!self::$supported) {
  194. $r = unlink($path);
  195. if ($cb) {
  196. $cb($path, $r);
  197. }
  198. return $r;
  199. }
  200. return eio_unlink($path, $pri, $cb, $path);
  201. }
  202. /**
  203. * Rename
  204. * @param string $path Path
  205. * @param string $newpath New path
  206. * @param callable $cb Callback
  207. * @param integer $pri Priority
  208. * @return resource|boolean
  209. */
  210. public static function rename($path, $newpath, $cb = null, $pri = EIO_PRI_DEFAULT)
  211. {
  212. $cb = CallbackWrapper::forceWrap($cb);
  213. if (!self::$supported) {
  214. $r = rename($path, $newpath);
  215. if ($cb) {
  216. $cb($path, $newpath, $r);
  217. }
  218. return $r;
  219. }
  220. return eio_rename($path, $newpath, $pri, $cb, $path);
  221. }
  222. /**
  223. * statvfs()
  224. * @param string $path Path
  225. * @param callable $cb Callback
  226. * @param integer $pri Priority
  227. * @return resource|false
  228. */
  229. public static function statvfs($path, $cb, $pri = EIO_PRI_DEFAULT)
  230. {
  231. $cb = CallbackWrapper::forceWrap($cb);
  232. if (!self::$supported) {
  233. $cb($path, false);
  234. return false;
  235. }
  236. return eio_statvfs($path, $pri, $cb, $path);
  237. }
  238. /**
  239. * lstat()
  240. * @param string $path Path
  241. * @param callable $cb Callback
  242. * @param integer $pri Priority
  243. * @return resource|true
  244. */
  245. public static function lstat($path, $cb, $pri = EIO_PRI_DEFAULT)
  246. {
  247. $cb = CallbackWrapper::forceWrap($cb);
  248. if (!self::$supported) {
  249. $cb($path, FileSystem::statPrepare(lstat($path)));
  250. return true;
  251. }
  252. return eio_lstat($path, $pri, function ($path, $stat) use ($cb) {
  253. $cb($path, FileSystem::statPrepare($stat));
  254. }, $path);
  255. }
  256. /**
  257. * realpath()
  258. * @param string $path Path
  259. * @param callable $cb Callback
  260. * @param integer $pri Priority
  261. * @return resource|true
  262. */
  263. public static function realpath($path, $cb, $pri = EIO_PRI_DEFAULT)
  264. {
  265. $cb = CallbackWrapper::forceWrap($cb);
  266. if (!self::$supported) {
  267. $cb($path, realpath($path));
  268. return true;
  269. }
  270. return eio_realpath($path, $pri, $cb, $path);
  271. }
  272. /**
  273. * sync()
  274. * @param callable $cb Callback
  275. * @param integer $pri Priority
  276. * @return resource|false
  277. */
  278. public static function sync($cb = null, $pri = EIO_PRI_DEFAULT)
  279. {
  280. $cb = CallbackWrapper::forceWrap($cb);
  281. if (!self::$supported) {
  282. if ($cb) {
  283. $cb(false);
  284. }
  285. return false;
  286. }
  287. return eio_sync($pri, $cb);
  288. }
  289. /**
  290. * statfs()
  291. * @param callable $cb Callback
  292. * @param integer $pri Priority
  293. * @return resource|false
  294. */
  295. public static function syncfs($cb = null, $pri = EIO_PRI_DEFAULT)
  296. {
  297. $cb = CallbackWrapper::forceWrap($cb);
  298. if (!self::$supported) {
  299. if ($cb) {
  300. $cb(false);
  301. }
  302. return false;
  303. }
  304. return eio_syncfs($pri, $cb);
  305. }
  306. /**
  307. * touch()
  308. * @param string $path Path
  309. * @param integer $mtime Last modification time
  310. * @param integer $atime Last access time
  311. * @param callable $cb Callback
  312. * @param integer $pri Priority
  313. * @return resource|boolean
  314. */
  315. public static function touch($path, $mtime, $atime = null, $cb = null, $pri = EIO_PRI_DEFAULT)
  316. {
  317. $cb = CallbackWrapper::forceWrap($cb);
  318. if (!FileSystem::$supported) {
  319. $r = touch($path, $mtime, $atime);
  320. if ($cb) {
  321. $cb($r);
  322. }
  323. return $r;
  324. }
  325. return eio_utime($path, $atime, $mtime, $pri, $cb, $path);
  326. }
  327. /**
  328. * Removes empty directory
  329. * @param string $path Path
  330. * @param callable $cb Callback
  331. * @param integer $pri Priority
  332. * @return resource|boolean
  333. */
  334. public static function rmdir($path, $cb = null, $pri = EIO_PRI_DEFAULT)
  335. {
  336. $cb = CallbackWrapper::forceWrap($cb);
  337. if (!FileSystem::$supported) {
  338. $r = rmdir($path);
  339. if ($cb) {
  340. $cb($path, $r);
  341. }
  342. return $r;
  343. }
  344. return eio_rmdir($path, $pri, $cb, $path);
  345. }
  346. /**
  347. * Creates directory
  348. * @param string $path Path
  349. * @param integer $mode Mode (octal)
  350. * @param callable $cb Callback
  351. * @param integer $pri Priority
  352. * @return resource|boolean
  353. */
  354. public static function mkdir($path, $mode, $cb = null, $pri = EIO_PRI_DEFAULT)
  355. {
  356. $cb = CallbackWrapper::forceWrap($cb);
  357. if (!FileSystem::$supported) {
  358. $r = mkdir($path, $mode);
  359. if ($cb) {
  360. $cb($path, $r);
  361. }
  362. return $r;
  363. }
  364. return eio_mkdir($path, $mode, $pri, $cb, $path);
  365. }
  366. /**
  367. * Readdir()
  368. * @param string $path Path
  369. * @param callable $cb = null Callback
  370. * @param integer $flags = null Flags
  371. * @param integer $pri = EIO_PRI_DEFAULT Priority
  372. * @return resource|true
  373. */
  374. public static function readdir($path, $cb = null, $flags = null, $pri = EIO_PRI_DEFAULT)
  375. {
  376. $cb = CallbackWrapper::forceWrap($cb);
  377. if (!FileSystem::$supported) {
  378. $r = glob($path);
  379. if ($cb) {
  380. $cb($path, $r);
  381. }
  382. return true;
  383. }
  384. return eio_readdir($path, $flags, $pri, $cb, $path);
  385. }
  386. /**
  387. * Truncate file
  388. * @param string $path Path
  389. * @param integer $offset Offset
  390. * @param callable $cb Callback
  391. * @param integer $pri Priority
  392. * @return resource|boolean
  393. */
  394. public static function truncate($path, $offset = 0, $cb = null, $pri = EIO_PRI_DEFAULT)
  395. {
  396. $cb = CallbackWrapper::forceWrap($cb);
  397. if (!FileSystem::$supported) {
  398. $fp = fopen($path, 'r+');
  399. $r = $fp && ftruncate($fp, $offset);
  400. if ($cb) {
  401. $cb($path, $r);
  402. }
  403. return $r;
  404. }
  405. return eio_truncate($path, $offset, $pri, $cb, $path);
  406. }
  407. /**
  408. * sendfile()
  409. * @param mixed $outfd File descriptor
  410. * @param string $path Path
  411. * @param callable $cb Callback
  412. * @param callable $startCb Start callback
  413. * @param integer $offset Offset
  414. * @param integer $length Length
  415. * @param integer $pri Priority
  416. * @return true Success
  417. */
  418. public static function sendfile(
  419. $outfd,
  420. $path,
  421. $cb,
  422. $startCb = null,
  423. $offset = 0,
  424. $length = null,
  425. $pri = EIO_PRI_DEFAULT
  426. ) {
  427. $cb = CallbackWrapper::forceWrap($cb);
  428. if (!self::$supported) {
  429. $cb($path, false);
  430. return false;
  431. }
  432. $noncache = true;
  433. FileSystem::open(
  434. $path,
  435. 'r!',
  436. function ($file) use ($cb, $noncache, $startCb, $path, $pri, $outfd, $offset, $length) {
  437. if (!$file) {
  438. $cb($path, false);
  439. return;
  440. }
  441. $file->sendfile(
  442. $outfd,
  443. function ($file, $success) use ($cb, $noncache) {
  444. $cb($file->path, $success);
  445. if ($noncache) {
  446. $file->close();
  447. }
  448. },
  449. $startCb,
  450. $offset,
  451. $length,
  452. $pri
  453. );
  454. },
  455. $pri
  456. );
  457. return true;
  458. }
  459. /**
  460. * Changes ownership of file/directory
  461. * @param string $path Path
  462. * @param integer $uid User ID
  463. * @param integer $gid Group ID
  464. * @param callable $cb = null Callback
  465. * @param integer $pri = EIO_PRI_DEFAULT Priority
  466. * @return resource|boolean
  467. */
  468. public static function chown($path, $uid, $gid = -1, $cb = null, $pri = EIO_PRI_DEFAULT)
  469. {
  470. $cb = CallbackWrapper::forceWrap($cb);
  471. if (!FileSystem::$supported) {
  472. $r = chown($path, $uid);
  473. if ($gid !== -1) {
  474. $r = $r && chgrp($path, $gid);
  475. }
  476. $cb($path, $r);
  477. return $r;
  478. }
  479. return eio_chown($path, $uid, $gid, $pri, $cb, $path);
  480. }
  481. /**
  482. * Reads whole file
  483. * @param string $path Path
  484. * @param callable $cb Callback (Path, Contents)
  485. * @param integer $pri Priority
  486. * @return resource|true
  487. */
  488. public static function readfile($path, $cb, $pri = EIO_PRI_DEFAULT)
  489. {
  490. $cb = CallbackWrapper::forceWrap($cb);
  491. if (!FileSystem::$supported) {
  492. $cb($path, file_get_contents($path));
  493. return true;
  494. }
  495. return FileSystem::open($path, 'r!', function ($file) use ($path, $cb, $pri) {
  496. if (!$file) {
  497. $cb($path, false);
  498. return;
  499. }
  500. $file->readAll($cb, $pri);
  501. }, null, $pri);
  502. }
  503. /**
  504. * Reads file chunk-by-chunk
  505. * @param string $path Path
  506. * @param callable $cb Callback (Path, Success)
  507. * @param callable $chunkcb Chunk callback (Path, Chunk)
  508. * @param integer $pri Priority
  509. * @return resource
  510. */
  511. public static function readfileChunked($path, $cb, $chunkcb, $pri = EIO_PRI_DEFAULT)
  512. {
  513. $cb = CallbackWrapper::forceWrap($cb);
  514. if (!FileSystem::$supported) {
  515. $chunkcb($path, $r = readfile($path));
  516. $cb($r !== false);
  517. return;
  518. }
  519. FileSystem::open($path, 'r!', function ($file) use ($path, $cb, $chunkcb, $pri) {
  520. if (!$file) {
  521. $cb($path, false);
  522. return;
  523. }
  524. $file->readAllChunked($cb, $chunkcb, $pri);
  525. }, null, $pri);
  526. }
  527. /**
  528. * Returns random temporary file name
  529. * @param string $dir Directory
  530. * @param string $prefix Prefix
  531. * @return string Path
  532. */
  533. public static function genRndTempnam($dir = null, $prefix = 'php')
  534. {
  535. if (!$dir) {
  536. $dir = sys_get_temp_dir();
  537. }
  538. static $n = 0;
  539. return $dir . '/' . $prefix . str_shuffle(
  540. md5(
  541. str_shuffle(
  542. microtime(true) . chr(mt_rand(0, 0xFF))
  543. . Daemon::$process->getPid() . chr(mt_rand(0, 0xFF))
  544. . (++$n) . mt_rand(0, mt_getrandmax())
  545. )
  546. )
  547. );
  548. }
  549. /**
  550. * Returns random temporary file name
  551. * @param string $dir Directory
  552. * @param string $prefix Prefix
  553. * @return string Path
  554. */
  555. public static function genRndTempnamPrefix($dir, $prefix)
  556. {
  557. if (!$dir) {
  558. $dir = sys_get_temp_dir();
  559. }
  560. return $dir . '/' . $prefix;
  561. }
  562. /**
  563. * Generates closure tempnam handler
  564. * @param $dir
  565. * @param $prefix
  566. * @param $cb
  567. * @param $tries
  568. */
  569. protected static function tempnamHandler($dir, $prefix, $cb, &$tries)
  570. {
  571. $cb = CallbackWrapper::forceWrap($cb);
  572. if (++$tries >= 3) {
  573. $cb(false);
  574. return;
  575. }
  576. $path = FileSystem::genRndTempnam($dir, $prefix);
  577. FileSystem::open($path, 'x+!', function ($file) use ($dir, $prefix, $cb, &$tries) {
  578. if (!$file) {
  579. static::tempnamHandler($dir, $prefix, $cb, $tries);
  580. return;
  581. }
  582. $cb($file);
  583. });
  584. }
  585. /**
  586. * Obtain exclusive temporary file
  587. * @param string $dir Directory
  588. * @param string $prefix Prefix
  589. * @param callable $cb Callback (File)
  590. * @return resource
  591. */
  592. public static function tempnam($dir, $prefix, $cb)
  593. {
  594. $cb = CallbackWrapper::forceWrap($cb);
  595. if (!FileSystem::$supported) {
  596. FileSystem::open(tempnam($dir, $prefix), 'w!', $cb);
  597. }
  598. $tries = 0;
  599. static::tempnamHandler($dir, $prefix, $cb, $tries);
  600. }
  601. /**
  602. * Open file
  603. * @param string $path Path
  604. * @param string $flags Flags
  605. * @param callable $cb Callback (File)
  606. * @param integer $mode Mode (see EIO_S_I* constants)
  607. * @param integer $pri Priority
  608. * @return resource
  609. */
  610. public static function open($path, $flags, $cb, $mode = null, $pri = EIO_PRI_DEFAULT)
  611. {
  612. $cb = CallbackWrapper::forceWrap($cb);
  613. if (!FileSystem::$supported) {
  614. $mode = File::convertFlags($flags, true);
  615. $fd = fopen($path, $mode);
  616. if (!$fd) {
  617. $cb(false);
  618. return false;
  619. }
  620. $file = new File($fd, $path);
  621. $cb($file);
  622. return true;
  623. }
  624. $fdCacheKey = $path . "\x00" . $flags;
  625. $noncache = mb_orig_strpos($flags, '!') !== false;
  626. $flags = File::convertFlags($flags);
  627. if (!$noncache && ($item = FileSystem::$fdCache->get($fdCacheKey))) { // cache hit
  628. $file = $item->getValue();
  629. if ($file === null) { // operation in progress
  630. $item->addListener($cb);
  631. } else { // hit
  632. $cb($file);
  633. }
  634. return null;
  635. } elseif (!$noncache) {
  636. $item = FileSystem::$fdCache->put($fdCacheKey, null);
  637. $item->addListener($cb);
  638. }
  639. return eio_open($path, $flags, $mode, $pri, function ($path, $fd) use ($cb, $flags, $fdCacheKey, $noncache) {
  640. if ($fd === -1) {
  641. if ($noncache) {
  642. $cb(false);
  643. } else {
  644. FileSystem::$fdCache->put($fdCacheKey, false, self::$badFDttl);
  645. }
  646. return;
  647. }
  648. $file = new File($fd, $path);
  649. $file->append = ($flags | EIO_O_APPEND) === $flags;
  650. if ($file->append) {
  651. $file->stat(function ($file, $stat) use ($cb, $noncache, $fdCacheKey) {
  652. $file->offset = $stat['size'];
  653. if (!$noncache) {
  654. $file->fdCacheKey = $fdCacheKey;
  655. FileSystem::$fdCache->put($fdCacheKey, $file);
  656. } else {
  657. $cb($file);
  658. }
  659. });
  660. } else {
  661. if (!$noncache) {
  662. $file->fdCacheKey = $fdCacheKey;
  663. FileSystem::$fdCache->put($fdCacheKey, $file);
  664. } else {
  665. $cb($file);
  666. }
  667. }
  668. }, $path);
  669. }
  670. }
  671. if (!defined('EIO_PRI_DEFAULT')) {
  672. define('EIO_PRI_DEFAULT', 0);
  673. }