filesystem.inc

  1. 8.0.x includes/filesystem.inc
  2. 6.x includes/filesystem.inc
  3. 7.x includes/filesystem.inc
  4. 5.x includes/filesystem.inc
  5. master includes/filesystem.inc

Filesystem utilities.

Functions

Namesort descending Description
drush_copy_dir Copy $src to $dest.
drush_correct_absolute_path_for_exec If we are going to pass a path to exec or proc_open, then we need to fix it up under CYGWIN or MINGW. In both of these environments, PHP works with absolute paths such as "C:\path". CYGWIN expects these to be converted to…
drush_delete_dir Deletes the specified file or directory and everything inside it.
drush_delete_dir_contents Deletes the contents of a directory.
drush_delete_tmp_dir Deletes the provided file or folder and everything inside it. This function explicitely tries to delete read-only files / folders.
drush_dir_md5 Calculates a single md5 hash for all files a directory (incuding subdirectories)
drush_file_append_data Simple helper function to append data to a given file.
drush_file_not_empty Test to see if a file exists and is not empty
drush_find_tmp Returns the path to a temporary directory.
drush_is_absolute_path Determines whether the provided path is absolute or not on the specified O.S. -- starts with "/" on *nix, or starts with "[A-Z]:\" or "[A-Z]:/" on Windows.
drush_is_nested_directory Return 'TRUE' if one directory is located anywhere inside the other.
drush_mkdir Cross-platform compatible helper function to recursively create a directory tree.
drush_move_dir Move $src to $dest.
drush_normalize_path Makes sure the path has only path separators native for the current operating system
drush_preflight_backup_dir Decide where our backup directory should go
drush_prepare_backup_dir Prepare a backup directory
drush_program_exists
drush_register_file_for_deletion Any file passed in to this function will be deleted when drush exits.
drush_save_data_to_temp_file Save a string to a temporary file. Does not depend on Drupal's API. The temporary file will be automatically deleted when drush exits.
drush_scan_directory Finds all files that match a given mask in a given directory. Directories and files beginning with a period are excluded; this prevents hidden files and directories (such as SVN working directories and GIT repositories) from being scanned.
drush_tempdir Creates a temporary directory and return its path.
drush_tempnam Creates a temporary file, and registers it so that it will be deleted when drush exits. Whenever possible, drush_save_data_to_temp_file() should be used instead of this function.
drush_trim_path Remove the trailing DIRECTORY_SEPARATOR from a path. Will actually remove either / or \ on Windows.
_drush_delete_registered_files Delete all of the registered temporary files.
_drush_recursive_copy Internal function called by drush_copy_dir; do not use directly.

Constants

Namesort descending Description
FILE_EXISTS_ABORT Behavior for drush_copy_dir() and drush_move_dir() when destinations exist.
FILE_EXISTS_MERGE
FILE_EXISTS_OVERWRITE

File

includes/filesystem.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Filesystem utilities.
  5. */
  6. use Webmozart\PathUtil\Path;
  7. /**
  8. * @defgroup filesystemfunctions Filesystem convenience functions.
  9. * @{
  10. */
  11. /**
  12. * Behavior for drush_copy_dir() and drush_move_dir() when destinations exist.
  13. */
  14. define('FILE_EXISTS_ABORT', 0);
  15. define('FILE_EXISTS_OVERWRITE', 1);
  16. define('FILE_EXISTS_MERGE', 2);
  17. /**
  18. * Determines whether the provided path is absolute or not
  19. * on the specified O.S. -- starts with "/" on *nix, or starts
  20. * with "[A-Z]:\" or "[A-Z]:/" on Windows.
  21. */
  22. function drush_is_absolute_path($path, $os = NULL) {
  23. // Relative paths will never start with a '/', even on Windows,
  24. // so it is safe to just call all paths that start with a '/'
  25. // absolute. This simplifies things for Windows with CYGWIN / MINGW / CWRSYNC,
  26. // where absolute paths sometimes start c:\path and sometimes
  27. // start /cygdrive/c/path.
  28. if ($path[0] == '/') {
  29. return TRUE;
  30. }
  31. if (drush_is_windows($os)) {
  32. return preg_match('@^[a-zA-Z]:[\\\/]@', $path);
  33. }
  34. return FALSE;
  35. }
  36. /**
  37. * If we are going to pass a path to exec or proc_open,
  38. * then we need to fix it up under CYGWIN or MINGW. In
  39. * both of these environments, PHP works with absolute paths
  40. * such as "C:\path". CYGWIN expects these to be converted
  41. * to "/cygdrive/c/path" and MINGW expects these to be converted
  42. * to "/c/path"; otherwise, the exec will not work.
  43. *
  44. * This call does nothing if the parameter is not an absolute
  45. * path, or we are not running under CYGWIN / MINGW.
  46. *
  47. * UPDATE: It seems I was mistaken; this is only necessary if we
  48. * are using cwRsync. We do not need to correct every path to
  49. * exec or proc_open (thank god).
  50. */
  51. function drush_correct_absolute_path_for_exec($path, $os = NULL) {
  52. if (drush_is_windows() && drush_is_absolute_path($path, "WINNT")) {
  53. if (drush_is_mingw($os)) {
  54. $path = preg_replace('/(\w):/', '/${1}', str_replace('\\', '/', $path));
  55. }
  56. elseif (drush_is_cygwin($os)) {
  57. $path = preg_replace('/(\w):/', '/cygdrive/${1}', str_replace('\\', '/', $path));
  58. }
  59. }
  60. return $path;
  61. }
  62. /**
  63. * Remove the trailing DIRECTORY_SEPARATOR from a path.
  64. * Will actually remove either / or \ on Windows.
  65. */
  66. function drush_trim_path($path, $os = NULL) {
  67. if (drush_is_windows($os)) {
  68. return rtrim($path, '/\\');
  69. }
  70. else {
  71. return rtrim($path, '/');
  72. }
  73. }
  74. /**
  75. * Makes sure the path has only path separators native for the current operating system
  76. */
  77. function drush_normalize_path($path) {
  78. if (drush_is_windows()) {
  79. $path = str_replace('/', '\\', strtolower($path));
  80. }
  81. else {
  82. $path = str_replace('\\', '/', $path);
  83. }
  84. return drush_trim_path($path);
  85. }
  86. /**
  87. * Calculates a single md5 hash for all files a directory (incuding subdirectories)
  88. */
  89. function drush_dir_md5($dir) {
  90. $flist = drush_scan_directory($dir, '/./', array('.', '..'), 0, TRUE, 'filename', 0, TRUE);
  91. $hashes = array();
  92. foreach ($flist as $f) {
  93. $sum = array();
  94. exec('cksum ' . escapeshellarg($f->filename), $sum);
  95. $hashes[] = trim(str_replace(array($dir), array(''), $sum[0]));
  96. }
  97. sort($hashes);
  98. return md5(implode("\n", $hashes));
  99. }
  100. /**
  101. * Deletes the specified file or directory and everything inside it.
  102. *
  103. * Usually respects read-only files and folders. To do a forced delete use
  104. * drush_delete_tmp_dir() or set the parameter $forced.
  105. *
  106. * @param string $dir
  107. * The file or directory to delete.
  108. * @param bool $force
  109. * Whether or not to try everything possible to delete the directory, even if
  110. * it's read-only. Defaults to FALSE.
  111. * @param bool $follow_symlinks
  112. * Whether or not to delete symlinked files. Defaults to FALSE--simply
  113. * unlinking symbolic links.
  114. *
  115. * @return bool
  116. * FALSE on failure, TRUE if everything was deleted.
  117. */
  118. function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) {
  119. // Do not delete symlinked files, only unlink symbolic links
  120. if (is_link($dir) && !$follow_symlinks) {
  121. return unlink($dir);
  122. }
  123. // Allow to delete symlinks even if the target doesn't exist.
  124. if (!is_link($dir) && !file_exists($dir)) {
  125. return TRUE;
  126. }
  127. if (!is_dir($dir)) {
  128. if ($force) {
  129. // Force deletion of items with readonly flag.
  130. @chmod($dir, 0777);
  131. }
  132. return unlink($dir);
  133. }
  134. if (drush_delete_dir_contents($dir, $force) === FALSE) {
  135. return FALSE;
  136. }
  137. if ($force) {
  138. // Force deletion of items with readonly flag.
  139. @chmod($dir, 0777);
  140. }
  141. return rmdir($dir);
  142. }
  143. /**
  144. * Deletes the contents of a directory.
  145. *
  146. * @param string $dir
  147. * The directory to delete.
  148. * @param bool $force
  149. * Whether or not to try everything possible to delete the contents, even if
  150. * they're read-only. Defaults to FALSE.
  151. *
  152. * @return bool
  153. * FALSE on failure, TRUE if everything was deleted.
  154. */
  155. function drush_delete_dir_contents($dir, $force = FALSE) {
  156. $scandir = @scandir($dir);
  157. if (!is_array($scandir)) {
  158. return FALSE;
  159. }
  160. foreach ($scandir as $item) {
  161. if ($item == '.' || $item == '..') {
  162. continue;
  163. }
  164. if ($force) {
  165. @chmod($dir, 0777);
  166. }
  167. if (!drush_delete_dir($dir . '/' . $item, $force)) {
  168. return FALSE;
  169. }
  170. }
  171. return TRUE;
  172. }
  173. /**
  174. * Deletes the provided file or folder and everything inside it.
  175. * This function explicitely tries to delete read-only files / folders.
  176. *
  177. * @param $dir
  178. * The directory to delete
  179. * @return
  180. * FALSE on failure, TRUE if everything was deleted
  181. */
  182. function drush_delete_tmp_dir($dir) {
  183. return drush_delete_dir($dir, TRUE);
  184. }
  185. /**
  186. * Copy $src to $dest.
  187. *
  188. * @param $src
  189. * The directory to copy.
  190. * @param $dest
  191. * The destination to copy the source to, including the new name of
  192. * the directory. To copy directory "a" from "/b" to "/c", then
  193. * $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename
  194. * it to "d", then $dest = "/c/d".
  195. * @param $overwrite
  196. * Action to take if destination already exists.
  197. * - FILE_EXISTS_OVERWRITE - completely removes existing directory.
  198. * - FILE_EXISTS_ABORT - aborts the operation.
  199. * - FILE_EXISTS_MERGE - Leaves existing files and directories in place.
  200. * @return
  201. * TRUE on success, FALSE on failure.
  202. */
  203. function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) {
  204. // Preflight based on $overwrite if $dest exists.
  205. if (file_exists($dest)) {
  206. if ($overwrite === FILE_EXISTS_OVERWRITE) {
  207. drush_op('drush_delete_dir', $dest, TRUE);
  208. }
  209. elseif ($overwrite === FILE_EXISTS_ABORT) {
  210. return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
  211. }
  212. elseif ($overwrite === FILE_EXISTS_MERGE) {
  213. // $overwrite flag may indicate we should merge instead.
  214. drush_log(dt('Merging existing !dest directory', array('!dest' => $dest)));
  215. }
  216. }
  217. // $src readable?
  218. if (!is_readable($src)) {
  219. return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  220. }
  221. // $dest writable?
  222. if (!is_writable(dirname($dest))) {
  223. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  224. }
  225. // Try to do a recursive copy.
  226. if (@drush_op('_drush_recursive_copy', $src, $dest)) {
  227. return TRUE;
  228. }
  229. return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest)));
  230. }
  231. /**
  232. * Internal function called by drush_copy_dir; do not use directly.
  233. */
  234. function _drush_recursive_copy($src, $dest) {
  235. // all subdirectories and contents:
  236. if(is_dir($src)) {
  237. if (!drush_mkdir($dest, TRUE)) {
  238. return FALSE;
  239. }
  240. $dir_handle = opendir($src);
  241. while($file = readdir($dir_handle)) {
  242. if ($file != "." && $file != "..") {
  243. if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) {
  244. return FALSE;
  245. }
  246. }
  247. }
  248. closedir($dir_handle);
  249. }
  250. elseif (is_link($src)) {
  251. symlink(readlink($src), $dest);
  252. }
  253. elseif (!copy($src, $dest)) {
  254. return FALSE;
  255. }
  256. // Preserve file modification time.
  257. // https://github.com/drush-ops/drush/pull/1146
  258. touch($dest, filemtime($src));
  259. // Preserve execute permission.
  260. if (!is_link($src) && !drush_is_windows()) {
  261. // Get execute bits of $src.
  262. $execperms = fileperms($src) & 0111;
  263. // Apply execute permissions if any.
  264. if ($execperms > 0) {
  265. $perms = fileperms($dest) | $execperms;
  266. chmod($dest, $perms);
  267. }
  268. }
  269. return TRUE;
  270. }
  271. /**
  272. * Move $src to $dest.
  273. *
  274. * If the php 'rename' function doesn't work, then we'll do copy & delete.
  275. *
  276. * @param $src
  277. * The directory to move.
  278. * @param $dest
  279. * The destination to move the source to, including the new name of
  280. * the directory. To move directory "a" from "/b" to "/c", then
  281. * $src = "/b/a" and $dest = "/c/a". To move "a" to "/c" and rename
  282. * it to "d", then $dest = "/c/d" (just like php rename function).
  283. * @param $overwrite
  284. * If TRUE, the destination will be deleted if it exists.
  285. * @return
  286. * TRUE on success, FALSE on failure.
  287. */
  288. function drush_move_dir($src, $dest, $overwrite = FALSE) {
  289. // Preflight based on $overwrite if $dest exists.
  290. if (file_exists($dest)) {
  291. if ($overwrite) {
  292. drush_op('drush_delete_dir', $dest, TRUE);
  293. }
  294. else {
  295. return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
  296. }
  297. }
  298. // $src readable?
  299. if (!drush_op('is_readable', $src)) {
  300. return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  301. }
  302. // $dest writable?
  303. if (!drush_op('is_writable', dirname($dest))) {
  304. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  305. }
  306. // Try rename. It will fail if $src and $dest are not in the same partition.
  307. if (@drush_op('rename', $src, $dest)) {
  308. return TRUE;
  309. }
  310. // Eventually it will create an empty file in $dest. See
  311. // http://www.php.net/manual/es/function.rename.php#90025
  312. elseif (is_file($dest)) {
  313. drush_op('unlink', $dest);
  314. }
  315. // If 'rename' fails, then we will use copy followed
  316. // by a delete of the source.
  317. if (drush_copy_dir($src, $dest)) {
  318. drush_op('drush_delete_dir', $src, TRUE);
  319. return TRUE;
  320. }
  321. return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('!src' => $src, '!dest' => $dest)));
  322. }
  323. /**
  324. * Cross-platform compatible helper function to recursively create a directory tree.
  325. *
  326. * @param path
  327. * Path to directory to create.
  328. * @param required
  329. * If TRUE, then drush_mkdir will call drush_set_error on failure.
  330. *
  331. * Callers should *always* do their own error handling after calling drush_mkdir.
  332. * If $required is FALSE, then a different location should be selected, and a final
  333. * error message should be displayed if no usable locations can be found.
  334. * @see drush_directory_cache().
  335. * If $required is TRUE, then the execution of the current command should be
  336. * halted if the required directory cannot be created.
  337. */
  338. function drush_mkdir($path, $required = TRUE) {
  339. if (!is_dir($path)) {
  340. if (drush_mkdir(dirname($path))) {
  341. if (@mkdir($path)) {
  342. return TRUE;
  343. }
  344. elseif (is_dir($path) && is_writable($path)) {
  345. // The directory was created by a concurrent process.
  346. return TRUE;
  347. }
  348. else {
  349. if (!$required) {
  350. return FALSE;
  351. }
  352. if (is_writable(dirname($path))) {
  353. return drush_set_error('DRUSH_CREATE_DIR_FAILURE', dt('Unable to create !dir.', array('!dir' => preg_replace('/\w+\/\.\.\//', '', $path))));
  354. }
  355. else {
  356. return drush_set_error('DRUSH_PARENT_NOT_WRITABLE', dt('Unable to create !newdir in !dir. Please check directory permissions.', array('!newdir' => basename($path), '!dir' => realpath(dirname($path)))));
  357. }
  358. }
  359. }
  360. return FALSE;
  361. }
  362. else {
  363. if (!is_writable($path)) {
  364. if (!$required) {
  365. return FALSE;
  366. }
  367. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Directory !dir exists, but is not writable. Please check directory permissions.', array('!dir' => realpath($path))));
  368. }
  369. return TRUE;
  370. }
  371. }
  372. /*
  373. * Determine if program exists on user's PATH.
  374. *
  375. * @return bool|null
  376. */
  377. function drush_program_exists($program) {
  378. if (drush_has_bash()) {
  379. $bucket = drush_bit_bucket();
  380. return drush_op_system("command -v $program >$bucket 2>&1") === 0 ? TRUE : FALSE;
  381. }
  382. }
  383. /**
  384. * Save a string to a temporary file. Does not depend on Drupal's API.
  385. * The temporary file will be automatically deleted when drush exits.
  386. *
  387. * @param string $data
  388. * @param string $suffix
  389. * Append string to filename. use of this parameter if is discouraged. @see
  390. * drush_tempnam().
  391. * @return string
  392. * A path to the file.
  393. */
  394. function drush_save_data_to_temp_file($data, $suffix = NULL) {
  395. static $fp;
  396. $file = drush_tempnam('drush_', NULL, $suffix);
  397. $fp = fopen($file, "w");
  398. fwrite($fp, $data);
  399. $meta_data = stream_get_meta_data($fp);
  400. $file = $meta_data['uri'];
  401. fclose($fp);
  402. return $file;
  403. }
  404. /**
  405. * Returns the path to a temporary directory.
  406. *
  407. * This is a custom version of Drupal's file_directory_path().
  408. * We can't directly rely on sys_get_temp_dir() as this
  409. * path is not valid in some setups for Mac, and we want to honor
  410. * an environment variable (used by tests).
  411. */
  412. function drush_find_tmp() {
  413. static $temporary_directory;
  414. if (!isset($temporary_directory)) {
  415. $directories = array();
  416. // Get user specific and operating system temp folders from system environment variables.
  417. // See http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true
  418. $tempdir = getenv('TEMP');
  419. if (!empty($tempdir)) {
  420. $directories[] = $tempdir;
  421. }
  422. $tmpdir = getenv('TMP');
  423. if (!empty($tmpdir)) {
  424. $directories[] = $tmpdir;
  425. }
  426. // Operating system specific dirs.
  427. if (drush_is_windows()) {
  428. $windir = getenv('WINDIR');
  429. if (isset($windir)) {
  430. // WINDIR itself is not writable, but it always contains a /Temp dir,
  431. // which is the system-wide temporary directory on older versions. Newer
  432. // versions only allow system processes to use it.
  433. $directories[] = Path::join($windir, 'Temp');
  434. }
  435. }
  436. else {
  437. $directories[] = Path::canonicalize('/tmp');
  438. }
  439. $directories[] = Path::canonicalize(sys_get_temp_dir());
  440. foreach ($directories as $directory) {
  441. if (is_dir($directory) && is_writable($directory)) {
  442. $temporary_directory = $directory;
  443. break;
  444. }
  445. }
  446. if (empty($temporary_directory)) {
  447. // If no directory has been found, create one in cwd.
  448. $temporary_directory = Path::join(drush_cwd(), 'tmp');
  449. drush_mkdir($temporary_directory, TRUE);
  450. if (!is_dir($temporary_directory)) {
  451. return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory."));
  452. }
  453. drush_register_file_for_deletion($temporary_directory);
  454. }
  455. }
  456. return $temporary_directory;
  457. }
  458. /**
  459. * Creates a temporary file, and registers it so that
  460. * it will be deleted when drush exits. Whenever possible,
  461. * drush_save_data_to_temp_file() should be used instead
  462. * of this function.
  463. *
  464. * @param string $suffix
  465. * Append this suffix to the filename. Use of this parameter is discouraged as
  466. * it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052.
  467. * Originally added to support Oracle driver.
  468. */
  469. function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') {
  470. if ($tmp_dir == NULL) {
  471. $tmp_dir = drush_find_tmp();
  472. }
  473. $tmp_file = tempnam($tmp_dir, $pattern);
  474. drush_register_file_for_deletion($tmp_file);
  475. $tmp_file_with_suffix = $tmp_file . $suffix;
  476. drush_register_file_for_deletion($tmp_file_with_suffix);
  477. return $tmp_file_with_suffix;
  478. }
  479. /**
  480. * Creates a temporary directory and return its path.
  481. */
  482. function drush_tempdir() {
  483. $tmp_dir = drush_trim_path(drush_find_tmp());
  484. $tmp_dir .= '/' . 'drush_tmp_' . uniqid(time() . '_');
  485. drush_mkdir($tmp_dir);
  486. drush_register_file_for_deletion($tmp_dir);
  487. return $tmp_dir;
  488. }
  489. /**
  490. * Any file passed in to this function will be deleted
  491. * when drush exits.
  492. */
  493. function drush_register_file_for_deletion($file = NULL) {
  494. static $registered_files = array();
  495. if (isset($file)) {
  496. if (empty($registered_files)) {
  497. register_shutdown_function('_drush_delete_registered_files');
  498. }
  499. $registered_files[] = $file;
  500. }
  501. return $registered_files;
  502. }
  503. /**
  504. * Delete all of the registered temporary files.
  505. */
  506. function _drush_delete_registered_files() {
  507. $files_to_delete = drush_register_file_for_deletion();
  508. foreach ($files_to_delete as $file) {
  509. // We'll make sure that the file still exists, just
  510. // in case someone came along and deleted it, even
  511. // though they did not need to.
  512. if (file_exists($file)) {
  513. if (is_dir($file)) {
  514. drush_delete_dir($file, TRUE);
  515. }
  516. else {
  517. @chmod($file, 0777); // Make file writeable
  518. unlink($file);
  519. }
  520. }
  521. }
  522. }
  523. /**
  524. * Decide where our backup directory should go
  525. *
  526. * @param string $subdir
  527. * The name of the desired subdirectory(s) under drush-backups.
  528. * Usually a database name.
  529. */
  530. function drush_preflight_backup_dir($subdir = NULL) {
  531. $backup_dir = drush_get_context('DRUSH_BACKUP_DIR', drush_get_option('backup-location'));
  532. if (empty($backup_dir)) {
  533. // Try to use db name as subdir if none was provided.
  534. if (empty($subdir)) {
  535. $subdir = 'unknown';
  536. if ($sql = drush_sql_get_class()) {
  537. $db_spec = $sql->db_spec();
  538. $subdir = $db_spec['database'];
  539. }
  540. }
  541. // Save the date to be used in the backup directory's path name.
  542. $date = gmdate('YmdHis', $_SERVER['REQUEST_TIME']);
  543. $backup_dir = drush_get_option('backup-dir', Path::join(drush_server_home(), 'drush-backups'));
  544. $backup_dir = Path::join($backup_dir, $subdir, $date);
  545. drush_set_context('DRUSH_BACKUP_DIR', $backup_dir);
  546. }
  547. else {
  548. Path::canonicalize($backup_dir);
  549. }
  550. return $backup_dir;
  551. }
  552. /**
  553. * Prepare a backup directory
  554. */
  555. function drush_prepare_backup_dir($subdir = NULL) {
  556. $backup_dir = drush_preflight_backup_dir($subdir);
  557. $backup_parent = Path::getDirectory($backup_dir);
  558. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  559. if ((!empty($drupal_root)) && (strpos($backup_parent, $drupal_root) === 0)) {
  560. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.'));
  561. }
  562. if (!file_exists($backup_parent)) {
  563. if (!drush_mkdir($backup_parent, TRUE)) {
  564. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Unable to create backup directory !dir.', array('!dir' => $backup_parent)));
  565. }
  566. }
  567. if (!is_writable($backup_parent)) {
  568. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Backup directory !dir is not writable.', array('!dir' => $backup_parent)));
  569. }
  570. if (!drush_mkdir($backup_dir, TRUE)) {
  571. return FALSE;
  572. }
  573. return $backup_dir;
  574. }
  575. /**
  576. * Test to see if a file exists and is not empty
  577. */
  578. function drush_file_not_empty($file_to_test) {
  579. if (file_exists($file_to_test)) {
  580. clearstatcache();
  581. $stat = stat($file_to_test);
  582. if ($stat['size'] > 0) {
  583. return TRUE;
  584. }
  585. }
  586. return FALSE;
  587. }
  588. /**
  589. * Finds all files that match a given mask in a given directory.
  590. * Directories and files beginning with a period are excluded; this
  591. * prevents hidden files and directories (such as SVN working directories
  592. * and GIT repositories) from being scanned.
  593. *
  594. * @param $dir
  595. * The base directory for the scan, without trailing slash.
  596. * @param $mask
  597. * The regular expression of the files to find.
  598. * @param $nomask
  599. * An array of files/directories to ignore.
  600. * @param $callback
  601. * The callback function to call for each match.
  602. * @param $recurse_max_depth
  603. * When TRUE, the directory scan will recurse the entire tree
  604. * starting at the provided directory. When FALSE, only files
  605. * in the provided directory are returned. Integer values
  606. * limit the depth of the traversal, with zero being treated
  607. * identically to FALSE, and 1 limiting the traversal to the
  608. * provided directory and its immediate children only, and so on.
  609. * @param $key
  610. * The key to be used for the returned array of files. Possible
  611. * values are "filename", for the path starting with $dir,
  612. * "basename", for the basename of the file, and "name" for the name
  613. * of the file without an extension.
  614. * @param $min_depth
  615. * Minimum depth of directories to return files from.
  616. * @param $include_dot_files
  617. * If TRUE, files that begin with a '.' will be returned if they
  618. * match the provided mask. If FALSE, files that begin with a '.'
  619. * will not be returned, even if they match the provided mask.
  620. * @param $depth
  621. * Current depth of recursion. This parameter is only used internally and should not be passed.
  622. *
  623. * @return
  624. * An associative array (keyed on the provided key) of objects with
  625. * "path", "basename", and "name" members corresponding to the
  626. * matching files.
  627. */
  628. function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) {
  629. $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  630. $files = array();
  631. // Exclude Bower and Node directories.
  632. $nomask = array_merge($nomask, array('node_modules', 'bower_components'));
  633. if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) {
  634. while (FALSE !== ($file = readdir($handle))) {
  635. if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
  636. if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
  637. // Give priority to files in this folder by merging them in after any subdirectory files.
  638. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
  639. }
  640. elseif ($depth >= $min_depth && preg_match($mask, $file)) {
  641. // Always use this match over anything already set in $files with the same $$key.
  642. $filename = "$dir/$file";
  643. $basename = basename($file);
  644. $name = substr($basename, 0, strrpos($basename, '.'));
  645. $files[$$key] = new stdClass();
  646. $files[$$key]->filename = $filename;
  647. $files[$$key]->basename = $basename;
  648. $files[$$key]->name = $name;
  649. if ($callback) {
  650. drush_op($callback, $filename);
  651. }
  652. }
  653. }
  654. }
  655. closedir($handle);
  656. }
  657. return $files;
  658. }
  659. /**
  660. * Simple helper function to append data to a given file.
  661. *
  662. * @param string $file
  663. * The full path to the file to append the data to.
  664. * @param string $data
  665. * The data to append.
  666. *
  667. * @return boolean
  668. * TRUE on success, FALSE in case of failure to open or write to the file.
  669. */
  670. function drush_file_append_data($file, $data) {
  671. if (!$fd = fopen($file, 'a+')) {
  672. drush_set_error(dt("ERROR: fopen(@file, 'ab') failed", array('@file' => $file)));
  673. return FALSE;
  674. }
  675. if (!fwrite($fd, $data)) {
  676. drush_set_error(dt("ERROR: fwrite(@file) failed", array('@file' => $file)) . '<pre>' . $data);
  677. return FALSE;
  678. }
  679. return TRUE;
  680. }
  681. /**
  682. * Return 'TRUE' if one directory is located anywhere inside
  683. * the other.
  684. */
  685. function drush_is_nested_directory($base_dir, $test_is_nested) {
  686. $common = Path::getLongestCommonBasePath([$test_is_nested, $base_dir]);
  687. return $common == Path::canonicalize($base_dir);
  688. }
  689. /**
  690. * @} End of "defgroup filesystemfunctions".
  691. */