provision.path.inc 13 KB
Newer Older
1
<?php
2
// $Id$
3
4
5
6
7
8
9
10
11
/**
 * @defgroup pathhandling Managing paths, permissions and file ownership
 *
 * This group provides an interface to common path handling operations, through
 * the provision_path helper function, which will take care of verification and
 * any error logging required.
 */

/**
12
 * Perform tasks on a path.
13
 *
14
 * Perform tasks on a path, and logs error messages / codes on success or failure.
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 * This function will call another function which defines the functionality,
 * and exists to provide a consistent interface for file operations with error logging
 * integration.
 *
 * Many of the provision_path_$op functions are really simple, but are wrapped
 * in functions to provide a consistent interface for provision_path to operate
 * with.
 *
 * @param type
 *    The type of operation to perform. One of the following:
 *      writable - The $path can be written to.
 *      exists - The $path exists.
 *      is_dir - The $path is a directory.
 *      readable - The $path is readable.
 *      owner - The $path belongs to the user in $confirm.
 *      group - The $path belongs to the group in $confirm.
 *      mkdir - Create the $path directory.
 *      unlink - Delete the file $path.
33
 *      symlink - Create a symlink from $path to $confirm.
34
35
36
37
38
39
40
41
42
43
44
 *      rmdir - Delete the directory $path.
 *      chmod - Change the file permissions of $path to the octal value in $confirm.
 *      chown - Change the owner of $path to the user in $confirm.
 *      chgrp - Change the group of $path to the group in $confirm.
 *      switch_paths - Move $path to $confirm, and vice versa.
 *
 * @param path
 *    The path you want to perform the file operation on.
 *
 * @param confirm
 *    Confirm that the final value of the file operation matches this value.
45
 *    This value defaults to TRUE, which is sufficient for most file operations.
46
 *
47
 *    Certain tasks such as chmod, chown and chgp will attempt to change the
48
49
50
51
 *    properties of $path to match the value in $confirm, and then test that
 *    the change was completed succesfully afterwards.
 *    
 *    These exceptions are :
52
 *      symlink - $confirm is the path to the symlink being created
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 *      chmod - $confirm is an octal value denoting the desired file permissions.
 *      chown - $confirm is the name or user id you wish to change the file ownership to.
 *      chgrp - $confirm is the name of group id you wish to change the file group ownership to.
 *      switch_paths - $confirm is the path that you want to replace the $path with.
 *
 * @param succeed_message
 *    Log this as a notice into the logging system, if the operation completed succesfully.
 *
 * @param fail_message
 *    Log this as a error to the logging system, if the $error_codes parameter has been set,
 *    otherwise, log this as a warning. If the operation specifies an additional reason for
 *    the operation failing, it will be appended to this message.
 *
 * @param error_codes
 *    Generate these system level errors using the provision error bitmasks.
 *
 * @return
 *    Returns TRUE if the test against $confirm passed, otherwise returns FALSE.
 */
72
function provision_path($op, $path, $confirm = TRUE, $succeed_message = NULL, $fail_message = NULL, $error_codes = NULL) {
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
  # The code style is a bit weird here, but it's a bit easier to read this way.
  $func = "provision_path_" . $op;
  if (function_exists($func)) {
    // The reason variable is passed into the operation function, to allow the function
    // to specify an additional reason as to why the operation failed.
    $reason = '';

    $value = $func($path, $confirm, $reason);

    clearstatcache(); // this needs to be called, otherwise we get the old info 
    $tokens = array("@path" => $path, "@op" => $op, "@confirm" => $confirm);
    if ($reason) {
      $fail_message = $fail_message . " (" . $reason . ")";
    }
    $status = ($value == $confirm);
    if ($status) {
89
      if (!is_null($succeed_message)) {
90
        drush_log(dt($succeed_message, $tokens), 'message');      
91
92
93
94
95
      }
    }
    else {
      if ($error_codes) {
        // Trigger a sysem halting error
96
        if (!is_null($fail_message)) {
97
98
99
100
          drush_set_error($error_codes, dt($fail_message, $tokens));
        }
        else {
          drush_set_error($error_codes);
101
        }
102
103
104
      }
      else {
        // Trigger a warning
105
        if (!is_null($fail_message)) {
106
          drush_log(dt($fail_message, $tokens), 'warning');
107
        }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
      }
    }
    return $status;
  }
}


function provision_path_writable($path) {
  return is_writable($path);
}

function provision_path_exists($path) {
  return file_exists($path);
}

function provision_path_is_dir($path) {
  return is_dir($path);
}

function provision_path_readable($path) {
  return is_readable($path);
}

function provision_path_owner($path) {
  $info = posix_getpwuid(fileowner($path));
  return $info['name'];
}

function provision_path_group($path) {
  return filegroup($path);
}

function provision_path_mkdir($path) {
141
142
143
144
145
  if (version_compare(PHP_VERSION, '5.0.0', '<')) {
    return _provision_mkdir_recursive($path, 0770);
  } else {
    return mkdir($path, 0770, TRUE);
  }
146
147
148
}

function provision_path_rmdir($path) {
149
  return rmdir($path);
150
151
152
}

function provision_path_unlink($path) {
153
  return unlink($path);
154
155
156
157
158
159
160
}


/*
 * This is where the more complex file operations start
 */

161
162
163
164
function provision_path_chmod($path, &$perms, &$reason, $recursive = FALSE) {
  $func = ($recursive) ? '_provision_chmod_recursive' : 'chmod';

  if (!@$func($path, $perms)) {
165
    $reason = dt('chmod to @perm failed on @path', array('@perm' => sprintf('%o', $perms), '@path' => $path));
166
167
168
169
    return false;
  }
  clearstatcache(); // this needs to be called, otherwise we get the old info 
  $value = substr(sprintf('%o', fileperms($path)), -4);
170
  $perms = sprintf('%04o', $perms);
171
172
173
  return $value;
}

174
175
function provision_path_chown($path, &$owner, &$reason, $recursive = FALSE) {
  $func = ($recursive) ? '_provision_chown_recursive' : 'chown';
176
  if ($owner = provision_posix_username($owner)) {
177
    if (!$func($path, $owner)) {
178
      $reason = dt("chown to @owner failed on @path", array('@owner' => $owner, '@path' => $path)) ; 
179
180
181
    }
  }
  else {
182
    $reason = dt("the user does not exist");
183
184
185
186
187
188
  }

  clearstatcache(); // this needs to be called, otherwise we get the old info 
  return provision_posix_username(fileowner($path));
}

189
190
function provision_path_chgrp($path, &$gid, &$reason, $recursive = FALSE) {
  $func = ($recursive) ? '_provision_chgrp_recursive' : 'chgrp';
191
  if ($group = provision_posix_groupname($gid)) {
192
    if (provision_user_in_group(drush_get_option('script_user'), $gid)) {
193
      if ($func($path, $group)) {
194
195
196
        return $group;
      }
      else {
197
        $reason = dt("chgrp to @group failed on @path", array('@group' => $group, '@path' => $path));
198
199
200
      }
    }
    else {
201
      $reason = dt("@user is not in @group group", array("@user" => drush_get_option('script_user'), "@group" => $group));
202
203
    }
  }
204
  elseif (!@$func($path, $gid)) { # try to change the group anyways
205
    $reason = dt("the group does not exist");
206
207
208
209
210
211
  }

  clearstatcache(); // this needs to be called, otherwise we get the old info 
  return provision_posix_groupname(filegroup($path));
}

212
213
214
215
216
217
218
219
220
221
222
223
224
225

function provision_path_chmod_recursive($path, &$perms, &$reason) {
  return provision_path_chmod($path, $perms, $reason, TRUE);
}

function provision_path_chown_recursive($path, &$owner, &$reason) {
  return provision_path_chown($path, $owner, $reason, TRUE);
}

function provision_path_chgrp_recursive($path, &$gid, &$reason) {
  return provision_path_chgrp($path, $gid, $reason, TRUE);
}


226
227
function provision_path_switch_paths($path1, &$path2, &$reason) {
  //TODO : Add error reasons.
228
  $temp = $path1 .'.tmp';
229
230
231
232
233
234
235
  if (!file_exists($path1)) {
    return rename($path2, $path1);
  }
  elseif (!file_exists($path2)) {
    return rename($path1, $path2);
  }
  elseif (rename($path1, $temp)) { 
236
237
238
239
240
241
    if (rename($path2, $path1)) {
      if (rename($temp, $path2)) {
        return $path2; // path1 is now path2
      }
      else {
        // same .. just in reverse
242
        return rename($path1, $path2) && rename($temp, $path1);
243
244
245
246
      }
    }
    else {
      // same .. just in reverse
247
      return rename($temp, $path1);
248
249
250
    }   

  }
251
  return FALSE;
252
253
}

254
255
256
257
function provision_path_extract($path, &$target, &$reason) {
  if (file_exists($path) && is_readable($path)) {
    if (is_writeable(dirname($target)) && !file_exists($target) && !is_dir($target)) {
      mkdir($target);
258
259
260
261
262
      $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 -';
mig5's avatar
mig5 committed
263
      drush_log(dt('Running: %command in %target', array('%command' => sprintf($command, $path), '%target' => $target)));
264
265
      $result = provision_shell_exec($command, $path);
      chdir($oldcwd);
266
267

      if ($result && is_writeable(dirname($target)) && is_readable(dirname($target)) && is_dir($target)) {
268
269
270
271
        $target = TRUE;
        return TRUE;
      }
      else {
272
        $reason = dt("The file could not be extracted");
273
      }
274
275
    }
    else {
276
      $reason = dt("The target directory could not be written to");
277
278
279
280
      return false;
    }
  }
  else {
281
    $reason = dt("Backup file could not be opened");
282
283
284
285
286
    return false;
  }

}

287
288
function provision_path_symlink($path, &$target, &$reason) {
  if (file_exists($target) && !is_link($target)) {
289
    $reason = dt("A file already exists at @path");
290
291
292
    return FALSE;
  }
  if (is_link($target) && (readlink($target) != $path)) {
293
    $reason = dt("A symlink already exists at target, but it is pointing to @link", array("@link" => readlink($target)));
294
295
    return FALSE;
  }
Adrian Rossouw's avatar
Adrian Rossouw committed
296
  if (is_link($target) && (readlink($target) == $path)) {
297
298
299
300
301
302
303
304
    $target = TRUE;
    return TRUE;
  }
  if (symlink($path, $target)) {
    $target = TRUE;
    return TRUE;
  }
  else {
305
    $reason = dt('The symlink could not be created, an error has occured');
306
307
308
309
310
311
    return FALSE;
  }


}

312
313
314
/**
*@} end filegroup
 */
315
316
317
318
319

/**
 * Small helper function for creation of configuration directories.
 */
function _provision_create_dir($path, $name, $perms) {
320
  $exists = provision_path("exists",$path, TRUE ,
321
322
    $name . ' ' . dt("path exists."),
    $name . ' ' . dt("path does not exist.")
323
324
325
  );

  if (!$exists) {
326
    $exists = provision_path("mkdir", $path, TRUE,
327
328
      $name . ' ' . dt("path has been created."),
      $name . ' ' . dt("path could not be created."),
329
      'DRUSH_PERM_ERROR');
330
331
332
  }

  if ($exists) {
333
    provision_path("chown", $path, drush_get_option('script_user'), 
334
335
      $name . ' ' . dt("ownership of path has been changed to @confirm."),
      $name . ' ' . dt("ownership of path could not be changed to @confirm."),
336
      'DRUSH_PERM_ERROR');
337
338

    provision_path("chmod", $path, $perms, 
339
340
      $name . ' ' . dt("permissions of path have been changed to @confirm."),
      $name . ' ' . dt("permissions of path could not be changed to @confirm."),
341
      'DRUSH_PERM_ERROR' );
342

343
    $writable = provision_path("writable", $path, TRUE,
344
345
        $name . ' ' . dt("path is writable."),
        $name . ' ' . dt("path is not writable."),
346
        'DRUSH_PERM_ERROR');
347
348
349
350
351
352

  }

  return ($exists && $writable);
}

353
354
355
356
357
358
359
360
361
362
363
/**
 * Makes directory recursively, returns TRUE if exists or made (for PHP4 compatibility)
 * Code courtesy of: http://ca3.php.net/manual/en/function.mkdir.php#81656
 *
 * @param string $pathname The directory path.
 * @return boolean returns TRUE if exists or made or FALSE on failure.
 */
function _provision_mkdir_recursive($path, $mode) {
  is_dir(dirname($path)) || _provision_mkdir_recursive(dirname($path), $mode);
  return is_dir($path) || mkdir($path, $mode);
}
364

365
366
367
368
369
370
371
372
373
374
375
376
377
/**
 * 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 _provision_call_recursive($func, $path, $arg) {
  $status = 1;
anarcat's avatar
anarcat committed
378
379
  // 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
380
  if (!is_link($path) && ($dh = @opendir($path))) {
381
382
383
384
385
386
387
388
389
390
391
    while (($file = readdir($dh)) !== false) {
      if ($file != '.' && $file != '..') {
        $status = _provision_call_recursive($func, $path . "/" . $file, $arg) && $status;
      }
    }
    closedir($dh);
  }
  $status = $func($path, $arg) && $status;
  return $status;
}

392
393
/**
 * Chmod a directory recursively
394
 *
395
396
 */
function _provision_chmod_recursive($path, $filemode) {
397
   return _provision_call_recursive("chmod", $path, $filemode);
398
399
400
401
402
403
}

/**
 * Chown a directory recursively
 */
function _provision_chown_recursive($path, $owner) {
404
   return _provision_call_recursive("chown", $path, $owner);
405
406
407
408
409
410
}

/**
 * Chgrp a directory recursively
 */
function _provision_chgrp_recursive($path, $owner) {
411
   return _provision_call_recursive("chgrp", $path, $owner);
412
}