backend.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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')) {
  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. }