5 * Filesystem utilities.
9 use Symfony\Component\Filesystem\Filesystem;
10 use Webmozart\PathUtil\Path;
13 * @defgroup filesystemfunctions Filesystem convenience functions.
18 * Behavior for drush_copy_dir() when destinations exist.
20 define('FILE_EXISTS_ABORT', 0);
21 define('FILE_EXISTS_OVERWRITE', 1);
22 define('FILE_EXISTS_MERGE', 2);
25 * Deletes the specified file or directory and everything inside it.
27 * Usually respects read-only files and folders. To do a forced delete use
28 * drush_delete_tmp_dir() or set the parameter $forced.
31 * The file or directory to delete.
33 * Whether or not to try everything possible to delete the directory, even if
34 * it's read-only. Defaults to FALSE.
35 * @param bool $follow_symlinks
36 * Whether or not to delete symlinked files. Defaults to FALSE--simply
37 * unlinking symbolic links.
40 * FALSE on failure, TRUE if everything was deleted.
42 * @deprecated Use \Symfony\Component\Filesystem\Filesystem::remove.
44 function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) {
45 // Do not delete symlinked files, only unlink symbolic links
46 if (is_link($dir) && !$follow_symlinks) {
49 // Allow to delete symlinks even if the target doesn't exist.
50 if (!is_link($dir) && !file_exists($dir)) {
55 // Force deletion of items with readonly flag.
60 if (drush_delete_dir_contents($dir, $force) === FALSE) {
64 // Force deletion of items with readonly flag.
71 * Deletes the contents of a directory.
74 * The directory to delete.
76 * Whether or not to try everything possible to delete the contents, even if
77 * they're read-only. Defaults to FALSE.
80 * FALSE on failure, TRUE if everything was deleted.
82 function drush_delete_dir_contents($dir, $force = FALSE) {
83 $scandir = @scandir($dir);
84 if (!is_array($scandir)) {
88 foreach ($scandir as $item) {
89 if ($item == '.' || $item == '..') {
95 if (!drush_delete_dir($dir . '/' . $item, $force)) {
103 * Copy $src to $dest.
106 * The directory to copy.
108 * The destination to copy the source to, including the new name of
109 * the directory. To copy directory "a" from "/b" to "/c", then
110 * $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename
111 * it to "d", then $dest = "/c/d".
113 * Action to take if destination already exists.
114 * - FILE_EXISTS_OVERWRITE - completely removes existing directory.
115 * - FILE_EXISTS_ABORT - aborts the operation.
116 * - FILE_EXISTS_MERGE - Leaves existing files and directories in place.
118 * TRUE on success, FALSE on failure.
120 * @deprecated Use \Symfony\Component\Filesystem\Filesystem::copy.
122 function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) {
123 // Preflight based on $overwrite if $dest exists.
124 if (file_exists($dest)) {
125 if ($overwrite === FILE_EXISTS_OVERWRITE) {
126 drush_op('drush_delete_dir', $dest, TRUE);
128 elseif ($overwrite === FILE_EXISTS_ABORT) {
129 return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
131 elseif ($overwrite === FILE_EXISTS_MERGE) {
132 // $overwrite flag may indicate we should merge instead.
133 drush_log(dt('Merging existing !dest directory', array('!dest' => $dest)));
137 if (!is_readable($src)) {
138 return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
141 if (!is_writable(dirname($dest))) {
142 return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
144 // Try to do a recursive copy.
145 if (@_drush_recursive_copy($src, $dest)) {
149 return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest)));
153 * Internal function called by drush_copy_dir; do not use directly.
155 function _drush_recursive_copy($src, $dest) {
156 // all subdirectories and contents:
161 $dir_handle = opendir($src);
162 while($file = readdir($dir_handle)) {
163 if ($file != "." && $file != "..") {
164 if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) {
169 closedir($dir_handle);
171 elseif (is_link($src)) {
172 symlink(readlink($src), $dest);
174 elseif (!copy($src, $dest)) {
178 // Preserve file modification time.
179 // https://github.com/drush-ops/drush/pull/1146
180 touch($dest, filemtime($src));
182 // Preserve execute permission.
183 if (!is_link($src) && (!function_exists('drush_is_windows') || !drush_is_windows())) {
184 // Get execute bits of $src.
185 $execperms = fileperms($src) & 0111;
186 // Apply execute permissions if any.
187 if ($execperms > 0) {
188 $perms = fileperms($dest) | $execperms;
189 chmod($dest, $perms);
197 * Recursively create a directory tree.
200 * Path to directory to create.
202 * @throws IOException On any directory creation failure
203 * @deprecated See \Symfony\Component\Filesystem\Filesystem::mkdir.
205 function drush_mkdir($path) {
206 $fs = new Filesystem();
212 * Determine if program exists on user's PATH.
216 function drush_program_exists($program) {
217 if (drush_has_bash()) {
218 $bucket = drush_bit_bucket();
220 // Remove environment variables (eg. PGPASSFILE=) before testing program.
221 $program = preg_replace('#^([A-Z0-9]+=.+? )+#', '', $program);
223 // Dont't use drush_op_system() since we don't want output during tests.
224 system("command -v $program > $bucket 2>&1", $result_code);
225 return $result_code === 0 ? TRUE : FALSE;
230 * Save a string to a temporary file. Does not depend on Drupal's API.
231 * The temporary file will be automatically deleted when drush exits.
233 * @param string $data
234 * @param string $suffix
235 * Append string to filename. use of this parameter if is discouraged. @see
238 * A path to the file.
240 function drush_save_data_to_temp_file($data, $suffix = NULL) {
243 $file = drush_tempnam('drush_', NULL, $suffix);
244 $fp = fopen($file, "w");
246 $meta_data = stream_get_meta_data($fp);
247 $file = $meta_data['uri'];
254 * Returns the path to a temporary directory.
256 * @deprecated Use $this->getConfig()->tmp() in a ConfigAware command.
258 function drush_find_tmp() {
259 return Drush::config()->tmp();
263 * Creates a temporary file, and registers it so that
264 * it will be deleted when drush exits. Whenever possible,
265 * drush_save_data_to_temp_file() should be used instead
268 * @param string $suffix
269 * Append this suffix to the filename. Use of this parameter is discouraged as
270 * it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052.
271 * Originally added to support Oracle driver.
273 function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') {
274 if ($tmp_dir == NULL) {
275 $tmp_dir = Drush::config()->tmp();
277 $tmp_file = tempnam($tmp_dir, $pattern);
278 drush_register_file_for_deletion($tmp_file);
279 $tmp_file_with_suffix = $tmp_file . $suffix;
280 drush_register_file_for_deletion($tmp_file_with_suffix);
281 return $tmp_file_with_suffix;
285 * Creates a temporary directory and return its path.
287 function drush_tempdir() {
288 $tmp_dir = Path::join(Drush::config()->tmp(), 'drush_tmp_' . uniqid(time() . '_'));
290 $fs = new Filesystem();
291 $fs->mkdir($tmp_dir);
292 drush_register_file_for_deletion($tmp_dir);
298 * Any file passed in to this function will be deleted
301 function drush_register_file_for_deletion($file = NULL) {
302 static $registered_files = array();
305 if (empty($registered_files)) {
306 register_shutdown_function('_drush_delete_registered_files');
308 $registered_files[] = $file;
311 return $registered_files;
315 * Delete all of the registered temporary files.
317 function _drush_delete_registered_files() {
318 $files_to_delete = drush_register_file_for_deletion();
320 foreach ($files_to_delete as $file) {
321 // We'll make sure that the file still exists, just
322 // in case someone came along and deleted it, even
323 // though they did not need to.
324 if (file_exists($file)) {
326 drush_delete_dir($file, TRUE);
329 @chmod($file, 0777); // Make file writeable
337 * Test to see if a file exists and is not empty
339 function drush_file_not_empty($file_to_test) {
340 if (file_exists($file_to_test)) {
342 $stat = stat($file_to_test);
343 if ($stat['size'] > 0) {
351 * Return 'TRUE' if one directory is located anywhere inside
354 function drush_is_nested_directory($base_dir, $test_is_nested) {
355 $common = Path::getLongestCommonBasePath([$test_is_nested, $base_dir]);
356 return $common == Path::canonicalize($base_dir);
360 * @} End of "defgroup filesystemfunctions".