backend.inc

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

Drush backend API

When a drush command is called with the --backend option, it will buffer all output, and instead return a JSON encoded string containing all relevant information on the command that was just executed.

Through this mechanism, it is possible for Drush commands to invoke each other.

There are many cases where a command might wish to call another command in its own process, to allow the calling command to intercept and act on any errors that may occur in the script that was called.

A simple example is if there exists an 'update' command for running update.php on a specific site. The original command might download a newer version of a module for installation on a site, and then run the update script in a separate process, so that in the case of an error running a hook_update_n function, the module can revert to a previously made database backup, and the previously installed code.

By calling the script in a separate process, the calling script is insulated from any error that occurs in the called script, to the level that if a php code error occurs (ie: misformed file, missing parenthesis, whatever), it is still able to reliably handle any problems that occur.

This is nearly a RESTful API. Instead of : http://[server]/[apipath]/[command]?[arg1]=[value1],[arg2]=[value2]

It will call : [apipath] [command] --[arg1]=[value1] --[arg2]=[value2] --backend

[apipath] in this case will be the path to the drush.php file. [command] is the command you would call, for instance 'status'.

GET parameters will be passed as options to the script. POST parameters will be passed to the script as a JSON encoded associative array over STDIN.

Because of this standard interface, Drush commands can also be executed on external servers through SSH pipes, simply by prepending, 'ssh username@server.com' in front of the command.

If the key-based ssh authentication has been set up between the servers, this will just work. By default, drush is configured to disallow password authentication; if you would like to enter a password for every connection, then in your drushrc.php file, set $options['ssh-options'] so that it does NOT include '-o PasswordAuthentication=no'. See examples/example.drushrc.php.

The results from backend API calls can be fetched via a call to drush_backend_get_result().

See also

http://en.wikipedia.org/wiki/REST

Functions

Namesort descending Description
drush_backend_fork A small utility function to call a drush command in the background.
drush_backend_get_result Retrieves the results from the last call to backend_invoke.
drush_backend_invoke Invoke a drush backend command.
drush_backend_invoke_args A variant of drush_backend_invoke() which specifies command and arguments separately.
drush_backend_invoke_sitealias Execute a new local or remote command in a new process.
drush_backend_output
drush_backend_parse_output Parse output returned from a Drush command.
drush_backend_set_result
_drush_backend_argument_string Map the options to a string containing all the possible arguments and options.
_drush_backend_generate_command Generate a command to execute.
_drush_backend_generate_command_sitealias Generate a command to execute.
_drush_backend_get_stdin Read options fron STDIN during POST requests.
_drush_backend_integrate Integrate log messages and error statuses into the current process.
_drush_backend_invoke Create a new pipe with proc_open, and attempt to parse the output.
_drush_escape_option Return a properly formatted and escaped command line option
_drush_proc_open Call an external command using proc_open.

Constants

Namesort descending Description
DRUSH_BACKEND_OUTPUT_DELIMITER Identify the JSON encoded output from a command.

File

includes/backend.inc
View source
  1. <?php
  2. /**
  3. * @file Drush backend API
  4. *
  5. * When a drush command is called with the --backend option,
  6. * it will buffer all output, and instead return a JSON encoded
  7. * string containing all relevant information on the command that
  8. * was just executed.
  9. *
  10. * Through this mechanism, it is possible for Drush commands to
  11. * invoke each other.
  12. *
  13. * There are many cases where a command might wish to call another
  14. * command in its own process, to allow the calling command to
  15. * intercept and act on any errors that may occur in the script that
  16. * was called.
  17. *
  18. * A simple example is if there exists an 'update' command for running
  19. * update.php on a specific site. The original command might download
  20. * a newer version of a module for installation on a site, and then
  21. * run the update script in a separate process, so that in the case
  22. * of an error running a hook_update_n function, the module can revert
  23. * to a previously made database backup, and the previously installed code.
  24. *
  25. * By calling the script in a separate process, the calling script is insulated
  26. * from any error that occurs in the called script, to the level that if a
  27. * php code error occurs (ie: misformed file, missing parenthesis, whatever),
  28. * it is still able to reliably handle any problems that occur.
  29. *
  30. * This is nearly a RESTful API. @see http://en.wikipedia.org/wiki/REST
  31. *
  32. * Instead of :
  33. * http://[server]/[apipath]/[command]?[arg1]=[value1],[arg2]=[value2]
  34. *
  35. * It will call :
  36. * [apipath] [command] --[arg1]=[value1] --[arg2]=[value2] --backend
  37. *
  38. * [apipath] in this case will be the path to the drush.php file.
  39. * [command] is the command you would call, for instance 'status'.
  40. *
  41. * GET parameters will be passed as options to the script.
  42. * POST parameters will be passed to the script as a JSON encoded associative array over STDIN.
  43. *
  44. * Because of this standard interface, Drush commands can also be executed on
  45. * external servers through SSH pipes, simply by prepending, 'ssh username@server.com'
  46. * in front of the command.
  47. *
  48. * If the key-based ssh authentication has been set up between the servers,
  49. * this will just work. By default, drush is configured to disallow password
  50. * authentication; if you would like to enter a password for every connection,
  51. * then in your drushrc.php file, set $options['ssh-options'] so that it does NOT
  52. * include '-o PasswordAuthentication=no'. See examples/example.drushrc.php.
  53. *
  54. * The results from backend API calls can be fetched via a call to
  55. * drush_backend_get_result().
  56. */
  57. /**
  58. * Identify the JSON encoded output from a command.
  59. */
  60. define('DRUSH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END');
  61. function drush_backend_set_result($value) {
  62. if (drush_get_context('DRUSH_BACKEND')) {
  63. drush_set_context('BACKEND_RESULT', $value);
  64. }
  65. }
  66. /**
  67. * Retrieves the results from the last call to backend_invoke.
  68. *
  69. * @returns array
  70. * An associative array containing information from the last
  71. * backend invoke. The keys in the array include:
  72. *
  73. * - output: This item contains the textual output of
  74. * the command that was executed.
  75. * - object: Contains the PHP object representation of the
  76. * result of the command.
  77. * - self: The self object contains the alias record that was
  78. * used to select the bootstrapped site when the command was
  79. * executed.
  80. * - error_status: This item returns the error status for the
  81. * command. Zero means "no error".
  82. * - log: The log item contains an array of log messages from
  83. * the command execution ordered chronologically. Each log
  84. * entery is an associative array. A log entry contains
  85. * following items:
  86. * o type: The type of log entry, such as 'notice' or 'warning'
  87. * o message: The log message
  88. * o timestamp: The time that the message was logged
  89. * o memory: Available memory at the time that the message was logged
  90. * o error: The error code associated with the log message
  91. * (only for log entries whose type is 'error')
  92. * - error_log: The error_log item contains another representation
  93. * of entries from the log. Only log entries whose 'error' item
  94. * is set will appear in the error log. The error log is an
  95. * associative array whose key is the error code, and whose value
  96. * is an array of messages--one message for every log entry with
  97. * the same error code.
  98. * - context: The context item contains a representation of all option
  99. * values that affected the operation of the command, including both
  100. * the command line options, options set in a drushrc.php configuration
  101. * files, and options set from the alias record used with the command.
  102. */
  103. function drush_backend_get_result() {
  104. return drush_get_context('BACKEND_RESULT');
  105. }
  106. function drush_backend_output() {
  107. $data = array();
  108. $data['output'] = ob_get_contents();
  109. ob_end_clean();
  110. $result_object = drush_backend_get_result();
  111. if (isset($result_object)) {
  112. $data['object'] = $result_object;
  113. }
  114. $error = drush_get_error();
  115. $data['error_status'] = ($error) ? $error : DRUSH_SUCCESS;
  116. $data['log'] = drush_get_log(); // Append logging information
  117. // The error log is a more specific version of the log, and may be used by calling
  118. // scripts to check for specific errors that have occurred.
  119. $data['error_log'] = drush_get_error_log();
  120. // If there is a @self record, then include it in the result
  121. $self_record = drush_sitealias_get_record('@self');
  122. if (!empty($self_record)) {
  123. $site_context = drush_get_context('site', array());
  124. unset($site_context['config-file']);
  125. unset($site_context['context-path']);
  126. unset($self_record['loaded-config']);
  127. unset($self_record['#name']);
  128. $data['self'] = array_merge($site_context, $self_record);
  129. }
  130. // Return the options that were set at the end of the process.
  131. $data['context'] = drush_get_merged_options();
  132. if (!drush_get_context('DRUSH_QUIET')) {
  133. printf(DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data));
  134. }
  135. }
  136. /**
  137. * Parse output returned from a Drush command.
  138. *
  139. * @param string
  140. * The output of a drush command
  141. * @param integrate
  142. * Integrate the errors and log messages from the command into the current process.
  143. *
  144. * @return
  145. * An associative array containing the data from the external command, or the string parameter if it
  146. * could not be parsed successfully.
  147. */
  148. function drush_backend_parse_output($string, $integrate = TRUE) {
  149. $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)');
  150. preg_match("/$regex/s", $string, $match);
  151. if ($match[1]) {
  152. // we have our JSON encoded string
  153. $output = $match[1];
  154. // remove the match we just made and any non printing characters
  155. $string = trim(str_replace(sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string));
  156. }
  157. if ($output) {
  158. $data = json_decode($output, TRUE);
  159. if (is_array($data)) {
  160. if ($integrate) {
  161. _drush_backend_integrate($data);
  162. }
  163. return $data;
  164. }
  165. }
  166. return $string;
  167. }
  168. /**
  169. * Integrate log messages and error statuses into the current process.
  170. *
  171. * Output produced by the called script will be printed, errors will be set
  172. * and log messages will be logged locally.
  173. *
  174. * @param data
  175. * The associative array returned from the external command.
  176. */
  177. function _drush_backend_integrate($data) {
  178. if (is_array($data['log'])) {
  179. foreach($data['log'] as $log) {
  180. $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message'];
  181. if (!is_null($log['error'])) {
  182. drush_set_error($log['error'], $message);
  183. }
  184. else {
  185. drush_log($message, $log['type']);
  186. }
  187. }
  188. }
  189. // Output will either be printed, or buffered to the drush_backend_output command.
  190. if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) {
  191. drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output'])));
  192. }
  193. else {
  194. print ($data['output']);
  195. }
  196. }
  197. /**
  198. * Call an external command using proc_open.
  199. *
  200. * @param cmd
  201. * The command to execute. This command already needs to be properly escaped.
  202. * @param data
  203. * An associative array that will be JSON encoded and passed to the script being called.
  204. * Objects are not allowed, as they do not json_decode gracefully.
  205. *
  206. * @return
  207. * False if the command could not be executed, or did not return any output.
  208. * If it executed successfully, it returns an associative array containing the command
  209. * called, the output of the command, and the error code of the command.
  210. */
  211. function _drush_proc_open($cmd, $data = NULL, $context = NULL) {
  212. $descriptorspec = array(
  213. 0 => array("pipe", "r"), // stdin is a pipe that the child will read from
  214. 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
  215. 2 => array("pipe", "w") // stderr is a pipe the child will write to
  216. );
  217. if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('#override-simulated', $data)) {
  218. drush_print('proc_open: ' . $cmd);
  219. return FALSE;
  220. }
  221. $process = proc_open($cmd, $descriptorspec, $pipes, null, null, array('context' => $context));
  222. if (is_resource($process)) {
  223. if ($data) {
  224. fwrite($pipes[0], json_encode($data)); // pass the data array in a JSON encoded string
  225. }
  226. $info = stream_get_meta_data($pipes[1]);
  227. stream_set_blocking($pipes[1], TRUE);
  228. stream_set_timeout($pipes[1], 1);
  229. $string = '';
  230. while (!feof($pipes[1]) && !$info['timed_out']) {
  231. $string .= fgets($pipes[1], 4096);
  232. $info = stream_get_meta_data($pipes[1]);
  233. flush();
  234. };
  235. $info = stream_get_meta_data($pipes[2]);
  236. stream_set_blocking($pipes[2], TRUE);
  237. stream_set_timeout($pipes[2], 1);
  238. while (!feof($pipes[2]) && !$info['timed_out']) {
  239. $string .= fgets($pipes[2], 4096);
  240. $info = stream_get_meta_data($pipes[2]);
  241. flush();
  242. };
  243. fclose($pipes[0]);
  244. fclose($pipes[1]);
  245. fclose($pipes[2]);
  246. $code = proc_close($process);
  247. return array('cmd' => $cmd, 'output' => $string, 'code' => $code);
  248. }
  249. return FALSE;
  250. }
  251. /**
  252. * Invoke a drush backend command.
  253. *
  254. * @param command
  255. * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
  256. * @param data
  257. * Optional. An array containing options to pass to the call. Common options would be 'uri' if you want to call a command
  258. * on a different site, or 'root', if you want to call a command using a different Drupal installation.
  259. * Array items with a numeric key are treated as optional arguments to the command.
  260. * @param method
  261. * Optional. Defaults to 'GET'.
  262. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
  263. * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
  264. * For any other value, the $data array will be collapsed down into a set of command line options to the script.
  265. * @param integrate
  266. * Optional. Defaults to TRUE.
  267. * If TRUE, any error statuses or log messages will be integrated into the current process. This might not be what you want,
  268. * if you are writing a command that operates on multiple sites.
  269. * @param drush_path
  270. * Optional. Defaults to the current drush.php file on the local machine, and
  271. * to simply 'drush' (the drush script in the current PATH) on remote servers.
  272. * You may also specify a different drush.php script explicitly. You will need
  273. * to set this when calling drush on a remote server if 'drush' is not in the
  274. * PATH on that machine.
  275. * @param hostname
  276. * Optional. A remote host to execute the drush command on.
  277. * @param username
  278. * Optional. Defaults to the current user. If you specify this, you can choose which module to send.
  279. *
  280. * @deprecated Command name includes arguments, and these are not quote-escaped in any way.
  281. * @see drush_invoke_process("@self", $command, array($arg1, $arg2, ...), $data) for a better option.
  282. *
  283. * @return
  284. * If the command could not be completed successfully, FALSE.
  285. * If the command was completed, this will return an associative array containing the data from drush_backend_output().
  286. */
  287. function drush_backend_invoke($command, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL) {
  288. $args = explode(" ", $command);
  289. $command = array_shift($args);
  290. return drush_backend_invoke_args($command, $args, $data, $method, $integrate, $drush_path, $hostname, $username);
  291. }
  292. /**
  293. * A variant of drush_backend_invoke() which specifies command and arguments separately.
  294. *
  295. * @deprecated; do not call directly.
  296. * @see drush_invoke_process("@self", $command, $args, $data) for a better option.
  297. */
  298. function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL, $ssh_options = NULL) {
  299. $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username, $ssh_options);
  300. return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate);
  301. }
  302. /**
  303. * Execute a new local or remote command in a new process.
  304. *
  305. * @param site_record
  306. * An array containing information used to generate the command.
  307. * 'remote-host'
  308. * Optional. A remote host to execute the drush command on.
  309. * 'remote-user'
  310. * Optional. Defaults to the current user. If you specify this, you can choose which module to send.
  311. * 'ssh-options'
  312. * Optional. Defaults to "-o PasswordAuthentication=no"
  313. * 'path-aliases'
  314. * Optional; contains paths to folders and executables useful to the command.
  315. * '%drush-script'
  316. * Optional. Defaults to the current drush.php file on the local machine, and
  317. * to simply 'drush' (the drush script in the current PATH) on remote servers.
  318. * You may also specify a different drush.php script explicitly. You will need
  319. * to set this when calling drush on a remote server if 'drush' is not in the
  320. * PATH on that machine.
  321. * @param command
  322. * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
  323. * @param args
  324. * An array of arguments for the command.
  325. * @param data
  326. * Optional. An array containing options to pass to the remote script.
  327. * Array items with a numeric key are treated as optional arguments to the command.
  328. * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed.
  329. * This allows you to pass the left over options as a JSON encoded string, without duplicating data.
  330. * @param method
  331. * Optional. Defaults to 'GET'.
  332. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
  333. * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
  334. * For any other value, the $data array will be collapsed down into a set of command line options to the script.
  335. * @param integrate
  336. * Optional. Defaults to TRUE.
  337. * If TRUE, any error statuses or log messages will be integrated into the current process. This might not be what you want,
  338. * if you are writing a command that operates on multiple sites.
  339. *
  340. * @return
  341. * A text string representing a fully escaped command.
  342. *
  343. * @deprecated; do not call directly.
  344. * @see drush_invoke_process($site_record, $command, $args, $data) for a better option.
  345. */
  346. function drush_backend_invoke_sitealias($site_record, $command, $args, $data = array(), $method = 'GET', $integrate = TRUE) {
  347. $cmd = _drush_backend_generate_command_sitealias($site_record, $command, $args, $data, $method);
  348. return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate);
  349. }
  350. /**
  351. * Create a new pipe with proc_open, and attempt to parse the output.
  352. *
  353. * We use proc_open instead of exec or others because proc_open is best
  354. * for doing bi-directional pipes, and we need to pass data over STDIN
  355. * to the remote script.
  356. *
  357. * Exec also seems to exhibit some strangeness in keeping the returned
  358. * data intact, in that it modifies the newline characters.
  359. *
  360. * @param cmd
  361. * The complete command line call to use.
  362. * @param data
  363. * An associative array to pass to the remote script.
  364. * @param integrate
  365. * Integrate data from remote script with local process.
  366. *
  367. * @return
  368. * If the command could not be completed successfully, FALSE.
  369. * If the command was completed, this will return an associative array containing the data from drush_backend_output().
  370. */
  371. function _drush_backend_invoke($cmd, $data = null, $integrate = TRUE) {
  372. drush_log(dt('Running: !cmd', array('!cmd' => $cmd)), 'command');
  373. if (array_key_exists('#interactive', $data)) {
  374. drush_log(dt("executing !cmd", array('!cmd' => $cmd)));
  375. return drush_op_system($cmd);
  376. }
  377. else {
  378. $proc = _drush_proc_open($cmd, $data);
  379. if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $integrate) {
  380. drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error."));
  381. }
  382. if ($proc['output']) {
  383. $values = drush_backend_parse_output($proc['output'], $integrate);
  384. if (is_array($values)) {
  385. return $values;
  386. }
  387. else {
  388. return drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: %code)", array("!return" => $proc['output'], "%code" => $proc['code'])));
  389. }
  390. }
  391. }
  392. return FALSE;
  393. }
  394. /**
  395. * Generate a command to execute.
  396. *
  397. * @param command
  398. * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
  399. * @param args
  400. * An array of arguments for the command.
  401. * @param data
  402. * Optional. An array containing options to pass to the remote script.
  403. * Array items with a numeric key are treated as optional arguments to the command.
  404. * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed.
  405. * This allows you to pass the left over options as a JSON encoded string, without duplicating data.
  406. * @param method
  407. * Optional. Defaults to 'GET'.
  408. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
  409. * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
  410. * For any other value, the $data array will be collapsed down into a set of command line options to the script.
  411. * @param drush_path
  412. * Optional. Defaults to the current drush.php file on the local machine, and
  413. * to simply 'drush' (the drush script in the current PATH) on remote servers.
  414. * You may also specify a different drush.php script explicitly. You will need
  415. * to set this when calling drush on a remote server if 'drush' is not in the
  416. * PATH on that machine.
  417. * @param hostname
  418. * Optional. A remote host to execute the drush command on.
  419. * @param username
  420. * Optional. Defaults to the current user. If you specify this, you can choose which module to send.
  421. *
  422. * @return
  423. * A text string representing a fully escaped command.
  424. *
  425. * @deprecated Is not as flexible as recommended command. @see _drush_backend_generate_command_sitealias().
  426. */
  427. function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null, $ssh_options = NULL) {
  428. return _drush_backend_generate_command_sitealias(
  429. array(
  430. 'remote-host' => $hostname,
  431. 'remote-user' => $username,
  432. 'ssh-options' => $ssh_options,
  433. 'path-aliases' => array(
  434. '%drush-script' => $drush_path,
  435. ),
  436. ), $command, $args, $data, $method);
  437. }
  438. /**
  439. * Generate a command to execute.
  440. *
  441. * @param site_record
  442. * An array containing information used to generate the command.
  443. * 'remote-host'
  444. * Optional. A remote host to execute the drush command on.
  445. * 'remote-user'
  446. * Optional. Defaults to the current user. If you specify this, you can choose which module to send.
  447. * 'ssh-options'
  448. * Optional. Defaults to "-o PasswordAuthentication=no"
  449. * 'path-aliases'
  450. * Optional; contains paths to folders and executables useful to the command.
  451. * '%drush-script'
  452. * Optional. Defaults to the current drush.php file on the local machine, and
  453. * to simply 'drush' (the drush script in the current PATH) on remote servers.
  454. * You may also specify a different drush.php script explicitly. You will need
  455. * to set this when calling drush on a remote server if 'drush' is not in the
  456. * PATH on that machine.
  457. * @param command
  458. * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
  459. * @param args
  460. * An array of arguments for the command.
  461. * @param data
  462. * Optional. An array containing options to pass to the remote script.
  463. * Array items with a numeric key are treated as optional arguments to the command.
  464. * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed.
  465. * This allows you to pass the left over options as a JSON encoded string, without duplicating data.
  466. * @param method
  467. * Optional. Defaults to 'GET'.
  468. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
  469. * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
  470. * For any other value, the $data array will be collapsed down into a set of command line options to the script.
  471. *
  472. * @return
  473. * A text string representing a fully escaped command.
  474. */
  475. function _drush_backend_generate_command_sitealias($site_record, $command, $args, &$data, $method = 'GET') {
  476. $drush_path = null;
  477. $hostname = array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : null;
  478. $username = array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : null;
  479. $ssh_options = array_key_exists('ssh-options', $site_record) ? $site_record['ssh-options'] : null;
  480. $os = drush_os($site_record);
  481. $drush_path = NULL;
  482. if (array_key_exists('path-aliases', $site_record)) {
  483. if (array_key_exists('%drush-script', $site_record['path-aliases'])) {
  484. $drush_path = $site_record['path-aliases']['%drush-script'];
  485. }
  486. }
  487. if (drush_is_local_host($hostname)) {
  488. $hostname = null;
  489. }
  490. $drush_path = !is_null($drush_path) ? $drush_path : (is_null($hostname) ? DRUSH_COMMAND : 'drush'); // Call own drush.php file on local machines, or 'drush' on remote machines.
  491. $data['root'] = array_key_exists('root', $data) ? $data['root'] : drush_get_context('DRUSH_DRUPAL_ROOT');
  492. $data['uri'] = array_key_exists('uri', $data) ? $data['uri'] : drush_get_context('DRUSH_URI');
  493. $option_str = _drush_backend_argument_string($data, $method);
  494. foreach ($data as $key => $arg) {
  495. if (is_numeric($key)) {
  496. $args[] = $arg;
  497. unset($data[$key]);
  498. }
  499. }
  500. foreach ($args as $arg) {
  501. $command .= ' ' . drush_escapeshellarg($arg);
  502. }
  503. $interactive = ' ' . (empty($data['#interactive']) ? '' : ' > `tty`') . ' 2>&1';
  504. // @TODO: Implement proper multi platform / multi server support.
  505. $cmd = escapeshellcmd($drush_path) . " " . $option_str . " " . $command . (empty($data['#interactive']) ? " --backend" : "");
  506. if (!is_null($hostname)) {
  507. $username = (!is_null($username)) ? drush_escapeshellarg($username) . "@" : '';
  508. $ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no");
  509. $cmd = "ssh " . $ssh_options . " " . $username . drush_escapeshellarg($hostname) . " " . drush_escapeshellarg($cmd . ' 2>&1', $os) . $interactive;
  510. }
  511. else {
  512. $cmd .= $interactive;
  513. }
  514. return $cmd;
  515. }
  516. /**
  517. * A small utility function to call a drush command in the background.
  518. *
  519. * Takes the same parameters as drush_backend_invoke, but forks a new
  520. * process by calling the command using system() and adding a '&' at the
  521. * end of the command.
  522. *
  523. * Use this if you don't care what the return value of the command may be.
  524. */
  525. function drush_backend_fork($command, $data, $drush_path = null, $hostname = null, $username = null) {
  526. $data['quiet'] = TRUE;
  527. $args = explode(" ", $command);
  528. $command = array_shift($args);
  529. $cmd = "(" . _drush_backend_generate_command($command, $args, $data, 'GET', $drush_path, $hostname, $username) . ' &) > /dev/null';
  530. drush_op_system($cmd);
  531. }
  532. /**
  533. * Map the options to a string containing all the possible arguments and options.
  534. *
  535. * @param data
  536. * Optional. An array containing options to pass to the remote script.
  537. * Array items with a numeric key are treated as optional arguments to the command.
  538. * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed.
  539. * This allows you to pass the left over options as a JSON encoded string, without duplicating data.
  540. * @param method
  541. * Optional. Defaults to 'GET'.
  542. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over
  543. * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like.
  544. * For any other value, the $data array will be collapsed down into a set of command line options to the script.
  545. * @return
  546. * A properly formatted and escaped set of arguments and options to append to the drush.php shell command.
  547. */
  548. function _drush_backend_argument_string(&$data, $method = 'GET') {
  549. // Named keys are options, numerically indexed keys are optional arguments.
  550. $args = array();
  551. $options = array();
  552. foreach ($data as $key => $value) {
  553. if (!is_array($value) && !is_object($value) && !is_null($value) && ($value != '')) {
  554. if (is_numeric($key)) {
  555. $args[$key] = $value;
  556. }
  557. elseif (substr($key,0,1) != '#') {
  558. $options[$key] = $value;
  559. }
  560. }
  561. }
  562. if (array_key_exists('backend', $data)) {
  563. unset($data['backend']);
  564. }
  565. $special = array('root', 'uri'); // These should be in the command line.
  566. $option_str = '';
  567. foreach ($options as $key => $value) {
  568. if (($method != 'POST') || (($method == 'POST') && in_array($key, $special))) {
  569. $option_str .= _drush_escape_option($key, $value);
  570. unset($data[$key]); // Remove items in the data array.
  571. }
  572. }
  573. return $option_str;
  574. }
  575. /**
  576. * Return a properly formatted and escaped command line option
  577. *
  578. * @param key
  579. * The name of the option.
  580. * @param value
  581. * The value of the option.
  582. *
  583. * @return
  584. * If the value is set to TRUE, this function will return " --key"
  585. * In other cases it will return " --key='value'"
  586. */
  587. function _drush_escape_option($key, $value = TRUE) {
  588. if ($value !== TRUE) {
  589. $option_str = " --$key=" . escapeshellarg($value);
  590. }
  591. else {
  592. $option_str = " --$key";
  593. }
  594. return $option_str;
  595. }
  596. /**
  597. * Read options fron STDIN during POST requests.
  598. *
  599. * This function will read any text from the STDIN pipe,
  600. * and attempts to generate an associative array if valid
  601. * JSON was received.
  602. *
  603. * @return
  604. * An associative array of options, if successfull. Otherwise FALSE.
  605. */
  606. function _drush_backend_get_stdin() {
  607. $fp = fopen('php://stdin', 'r');
  608. stream_set_blocking($fp, FALSE);
  609. $string = stream_get_contents($fp);
  610. fclose($fp);
  611. if (trim($string)) {
  612. return json_decode($string, TRUE);
  613. }
  614. return FALSE;
  615. }