file.drush.inc 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
<?php
// $Id$
/**
 * @file Filesystem access module.
 *
 * Handle writing and syncing configuation files across multiple servers.
 * Provides an interface to common path handling operations, through the path
 * helper method, which will take care of verification and any error logging
 * required.
 */

include_once(dirname(__FILE__) . '/../provision.service.inc');

14 15
function file_provision_services() {
  return array('file' => 'local');
16 17 18
}

class provisionService_file extends provisionService {
19 20 21 22 23 24 25 26
  /**
   * Determine if $path can be written to.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
27
  function writable($path) {
28 29 30 31 32 33
    $this->_clear_state();

    $this->last_status = is_writable($path);
    $this->tokens = array('@path' => $path);

    return $this;
34 35
  }

36 37 38 39 40 41 42 43
  /**
   * Determine if $path exists.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
44
  function exists($path) {
45
    $this->_clear_state();
46

47 48 49 50
    $this->last_status = file_exists($path);
    $this->tokens = array('@path' => $path);

    return $this;
51 52
  }

53 54 55 56 57 58 59 60
  /**
   * Determine if $path is readable.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
61
  function readable($path) {
62
    $this->_clear_state();
63

64 65
    $this->last_status = is_readable($path);
    $this->tokens = array('@path' => $path);
66

67
    return $this;
68 69
  }

70 71 72 73 74 75 76 77
  /**
   * Create the $path directory.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
78
  function mkdir($path) {
79 80 81 82 83 84
    $this->_clear_state();

    $this->last_status = mkdir($path, 0770, TRUE);
    $this->tokens = array('@path' => $path);

    return $this;
85 86
  }

87 88 89 90 91 92 93 94
  /**
   * Delete the directory $path.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
95
  function rmdir($path) {
96 97 98 99 100 101
    $this->_clear_state();

    $this->last_status = rmdir($path);
    $this->tokens = array('@path' => $path);

    return $this;
102 103
  }

104 105 106 107 108 109 110 111
  /**
   * Delete the file $path.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   */
112
  function unlink($path) {
113 114 115 116 117 118
    $this->_clear_state();

    $this->last_status = unlink($path);
    $this->tokens = array('@path' => $path);

    return $this;
119 120
  }

121 122 123 124 125
  /**
   * Change the file permissions of $path to the octal value in $perms.
   *
   * @param $perms
   *   An octal value denoting the desired file permissions.
126
   */
127 128
  function chmod($path, $perms, $recursive = FALSE) {
    $this->_clear_state();
129

130
    $this->tokens = array('@path' => $path, '@perm' => sprintf('%o', $perms));
131

132
    $func = ($recursive) ? array($this, '_chmod_recursive') : 'chmod';
133
    if (!@call_user_func($func, $path, $perms)) {
134
      $this->tokens['@reason'] = dt('chmod to @perm failed on @path', array('@perm' => sprintf('%o', $perms), '@path' => $path));
135 136
    }
    clearstatcache(); // this needs to be called, otherwise we get the old info 
137 138 139
    $this->last_status = substr(sprintf('%o', fileperms($path)), -4) == sprintf('%04o', $perms);

    return $this;
140 141
  }

142 143 144
  /**
   * Change the owner of $path to the user in $owner.
   *
drumm's avatar
drumm committed
145
   * Sets @path, @uid, and @reason tokens for ->succeed and ->fail.
146 147 148 149 150 151 152 153 154 155
   *
   * @param $path
   *   The path you want to perform this operation on.
   * @param $owner
   *   The name or user id you wish to change the file ownership to.
   * @param $recursive
   *   TRUE to descend into subdirectories.
   */
  function chown($path, $owner, $recursive = FALSE) {
    $this->_clear_state();
drumm's avatar
drumm committed
156
    $this->tokens = array('@path' => $path, '@uid' => $owner);
157

drumm's avatar
drumm committed
158
    // We do not attempt to chown symlinks.
159
    if (is_link($path)) {
drumm's avatar
drumm committed
160
      return $this;
161
    } 
162

163 164 165
    $func = ($recursive) ? array($this, '_chown_recursive') : 'chown';
    if ($owner = provision_posix_username($owner)) {
      if (!call_user_func($func, $path, $owner)) {
166
        $this->tokens['@reason'] = dt("chown to @owner failed on @path", array('@owner' => $owner, '@path' => $path)) ; 
167 168 169
      }
    }
    else {
170
      $this->tokens['@reason'] = dt("the user does not exist");
171 172 173
    }

    clearstatcache(); // this needs to be called, otherwise we get the old info 
174 175 176
    $this->last_status = $owner == provision_posix_username(fileowner($path));

    return $this;
177 178
  }

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
  /**
   * Change the group of $path to the group in $gid.
   *
   * Sets @path, @gid, and @reason tokens for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   * @param $gid
   *   The name of group id you wish to change the file group ownership to.
   * @param $recursive
   *   TRUE to descend into subdirectories.
   */
  function chgrp($path, $gid, $recursive = FALSE) {
    $this->_clear_state();
    $this->tokens = array('@path' => $path, '@gid' => $gid);

drumm's avatar
drumm committed
195
    // We do not attempt to chown symlinks.
196
    if (is_link($path)) {
drumm's avatar
drumm committed
197
      return $this;
198
    } 
199

200 201
    $func = ($recursive) ? array($this, '_chgrp_recursive') : 'chgrp';
    if ($group = provision_posix_groupname($gid)) {
202
      if (provision_user_in_group($this->script_user, $gid)) {
203 204
        if (!call_user_func($func, $path, $group)) {
          $this->tokens['@reason'] = dt("chgrp to @group failed on @path", array('@group' => $group, '@path' => $path));
205 206 207
        }
      }
      else {
208
        $this->tokens['@reason'] = dt("@user is not in @group group", array("@user" => $this->script_user, "@group" => $group));
209 210 211
      }
    }
    elseif (!@call_user_func($func, $path, $gid)) { # try to change the group anyways
212
      $this->tokens['@reason'] = dt("the group does not exist");
213 214 215
    }

    clearstatcache(); // this needs to be called, otherwise we get the old info 
216
    $this->last_status = $group == provision_posix_groupname(filegroup($path));
217

218
    return $this;
219 220
  }

221 222 223 224 225 226 227 228 229 230
  /**
   * Move $path1 to $path2, and vice versa.
   *
   * @param $path1
   *   The path that you want to replace the $path2 with.
   * @param $path2
   *   The path that you want to replace the $path1 with.
   */
  function switch_paths($path1, $path2) {
    $this->_clear_state();
231

232
    $this->tokens = array('@path1' => $path1, '@path2' => $path2);
233

Adrian Rossouw's avatar
Adrian Rossouw committed
234 235
    $this->last_status = FALSE;

236
    //TODO : Add error reasons.
237
    $temp = $path1 . '.tmp';
238
    if (!file_exists($path1)) {
239
      $this->last_status = rename($path2, $path1);
240 241
    }
    elseif (!file_exists($path2)) {
242
      $this->last_status = rename($path1, $path2);
243 244 245 246
    }
    elseif (rename($path1, $temp)) { 
      if (rename($path2, $path1)) {
        if (rename($temp, $path2)) {
Adrian Rossouw's avatar
Adrian Rossouw committed
247
          $this->last_status = TRUE; // path1 is now path2
248 249 250
        }
        else {
          // same .. just in reverse
251
          $this->last_status = rename($path1, $path2) && rename($temp, $path1);
252 253 254 255
        }
      }
      else {
        // same .. just in reverse
256
        $this->last_status = rename($temp, $path1);
257 258
      }   
    }
259 260

    return $this;
261 262
  }

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
  /**
   * Extract gzip-compressed tar archive.
   *
   * Sets @path, @target, and @reason tokens for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to extract.
   * @param $target
   *   The destination path to extract to.
   */
  function extract($path, $target) {
    $this->_clear_state();

    $this->tokens = array('@path' => $path, '@target' => $target);

278 279
    if (file_exists($path) && is_readable($path)) {
      if (is_writeable(dirname($target)) && !file_exists($target) && !is_dir($target)) {
280
        $this->mkdir($target);
281 282 283 284 285 286 287 288 289 290
        $oldcwd = getcwd();
        // we need to do this because some retarded implementations of tar (e.g. SunOS) don't support -C
        chdir($target);
        // same here: some do not support -z
        $command = 'gunzip -c %s | tar pxf -';
        drush_log(dt('Running: %command in %target', array('%command' => sprintf($command, $path), '%target' => $target)));
        $result = provision_shell_exec($command, $path);
        chdir($oldcwd);

        if ($result && is_writeable(dirname($target)) && is_readable(dirname($target)) && is_dir($target)) {
291
          $this->last_status = TRUE;
292 293
        }
        else {
294 295
          $this->tokens['@reason'] = dt('The file could not be extracted');
          $this->last_status = FALSE;
296 297 298
        }
      }
      else {
299 300
        $this->tokens['@reason'] = dt('The target directory could not be written to');
        $this->last_status = FALSE;
301 302 303
      }
    }
    else {
304 305
      $this->tokens['@reason'] = dt('Backup file could not be opened');
      $this->last_status = FALSE;
306
    }
307 308

    return $this;
309 310
  }

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  /**
   * Create a symlink from $path to $target.
   *
   * Sets @path, @target, and @reason tokens for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   * @param $target
   *   The path you want the link to point to.
   */
  function symlink($path, $target) {
    $this->_clear_state();

    $this->tokens = array('@path' => $path, '@target' => $target);

326
    if (file_exists($target) && !is_link($target)) {
327 328
      $this->tokens['@reason'] = dt("A file already exists at @path");
      $this->last_status = FALSE;
329
    }
330 331 332
    elseif (is_link($target) && (readlink($target) != $path)) {
      $this->tokens['@reason'] = dt("A symlink already exists at target, but it is pointing to @link", array("@link" => readlink($target)));
      $this->last_status = FALSE;
333
    }
334 335
    elseif (is_link($target) && (readlink($target) == $path)) {
      $this->last_status = TRUE;
336
    }
337 338
    elseif (symlink($path, $target)) {
      $this->last_status = TRUE;
339 340
    }
    else {
341 342
      $this->tokens['@reason'] = dt('The symlink could not be created, an error has occured');
      $this->last_status = FALSE;
343
    }
344 345

    return $this;
346 347 348 349 350 351
  }

  /**
   * Small helper function for creation of configuration directories.
   */
  function create_dir($path, $name, $perms) {
352 353 354 355
    $exists = $this->exists($path)
      ->succeed($name . ' path @path exists.')
      ->fail($name . ' path @path does not exist.')
      ->status();
356 357

    if (!$exists) {
358 359 360 361
      $exists = $this->mkdir($path)
        ->succeed($name . ' path @path has been created.')
        ->fail($name . ' path @path could not be created.', 'DRUSH_PERM_ERROR')
        ->status();
362 363 364
    }

    if ($exists) {
365
      $this->chown($path, $this->script_user)
drumm's avatar
drumm committed
366 367
        ->succeed($name . ' ownership of @path has been changed to @uid.')
        ->fail($name . ' ownership of @path could not be changed to @uid.', 'DRUSH_PERM_ERROR');
368

369
      $this->chmod($path, $perms)
drumm's avatar
drumm committed
370 371
        ->succeed($name . ' permissions of @path have been changed to @perm.')
        ->fail($name . ' permissions of @path could not be changed to @perm.', 'DRUSH_PERM_ERROR');
372

373 374 375
      $this->writable($path)
        ->succeed($name . ' path @path is writable.')
        ->fail($name . ' path @path is not writable.', 'DRUSH_PERM_ERROR');
376 377
    }

378
    return $exists;
379 380
  }

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
  /**
   * Write $data to $path.
   *
   * Sets @path token for ->succeed and ->fail.
   *
   * @param $path
   *   The path you want to perform this operation on.
   * @param $data
   *   The data to write.
   * @param $flags
   *   The file_put_contents() flags to use.
   *
   * @see file_put_contents()
   */
  function file_put_contents($path, $data, $flags) {
    $this->_clear_state();

    $this->tokens = array('@path' => $path);
    $this->last_status = file_put_contents($path, $data, $flags) !== FALSE;

    return $this;
  }

404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
  /**
   * Walk the given tree recursively (depth first), calling a function on each file
   *
   * $func is not checked for existence and called directly with $path and $arg
   * for every file encountered.
   *
   * @param string $func a valid callback, usually chmod, chown or chgrp
   * @param string $path a path in the filesystem
   * @param string $arg the second argument to $func
   * @return boolean returns TRUE if every $func call returns true
   */
  function _call_recursive($func, $path, $arg) {
    $status = 1;
    // do not follow symlinks as it could lead to a DOS attack
    // consider someone creating a symlink from files/foo to ..: it would create an infinite loop
419 420 421 422 423 424
    if (!is_link($path)) {
      if ($dh = @opendir($path)) {
        while (($file = readdir($dh)) !== false) {
          if ($file != '.' && $file != '..') {
            $status = $this->_call_recursive($func, $path . "/" . $file, $arg) && $status;
          }
425
        }
426
        closedir($dh);
427
      }
428
      $status = call_user_func($func, $path, $arg) && $status;
429 430 431 432 433 434 435 436 437
    }
    return $status;
  }

  /**
   * Chmod a directory recursively
   *
   */
  function _chmod_recursive($path, $filemode) {
438
    return $this->_call_recursive('chmod', $path, $filemode);
439 440 441 442 443 444
  }

  /**
   * Chown a directory recursively
   */
  function _chown_recursive($path, $owner) {
445
    return $this->_call_recursive('chown', $path, $owner);
446 447 448 449 450
  }

  /**
   * Chgrp a directory recursively
   */
451 452
  function _chgrp_recursive($path, $group) {
    return $this->_call_recursive('chgrp', $path, $group);
453
  }
454 455 456

  /**
   * If necessary, sync files out to a remote server.
457 458 459 460 461
   *
   * @param $path
   *   Full path to sync.
   * @param $exclude_sites
   *   Exclude sites/*, except sites/*.
462
   */
463
  function sync($path = NULL, $exclude_sites = FALSE) {
drumm's avatar
drumm committed
464 465 466
    if (is_null($path)) {
      $path = $this->config_path;
    }
467
  }
468
}