talon.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /*
  2. * Copyright (c) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
  3. * All rights reserved.
  4. * This component and the accompanying materials are made available
  5. * under the terms of the License "Eclipse Public License v1.0"
  6. * which accompanies this distribution, and is available
  7. * at the URL "http://www.eclipse.org/legal/epl-v10.html".
  8. *
  9. * Initial Contributors:
  10. * Nokia Corporation - initial contribution.
  11. *
  12. * Contributors:
  13. *
  14. * Description:
  15. *
  16. */
  17. #ifdef HAS_WINSOCK2
  18. #include <winsock2.h>
  19. #include <ws2tcpip.h>
  20. #define WIN32_LEAN_AND_MEAN
  21. #endif
  22. #include <stdlib.h>
  23. #include <unistd.h>
  24. #include <string.h>
  25. #include <stdio.h>
  26. #include <ctype.h>
  27. #include <sys/types.h>
  28. #include <sys/time.h>
  29. #include <sys/stat.h>
  30. #include <fcntl.h>
  31. #include <stdarg.h>
  32. #include "talon_process.h"
  33. #include "sema.h"
  34. #include "buffer.h"
  35. #include "env.h"
  36. #include "../config.h"
  37. #ifdef HAS_GETCOMMANDLINE
  38. #include "chomp.h"
  39. #endif
  40. /* The output semaphore. */
  41. sbs_semaphore talon_sem;
  42. #define TALON_ATTEMPT_STRMAX 32
  43. #define RECIPETAG_STRMAX 2048
  44. #define STATUS_STRMAX 120
  45. #define WARNING_STRMAX 250
  46. #define TALONDELIMITER '|'
  47. #define VARNAMEMAX 100
  48. #define VARVALMAX 1024
  49. #define HOSTNAME_MAX 100
  50. #include "log.h"
  51. #ifdef HAS_MSVCRT
  52. /* Make all output handling binary */
  53. unsigned int _CRT_fmode = _O_BINARY;
  54. #endif
  55. double getseconds(void)
  56. {
  57. struct timeval tp;
  58. gettimeofday(&tp, NULL);
  59. return (double)tp.tv_sec + ((double)tp.tv_usec)/1000000.0L;
  60. }
  61. void prependattributes(buffer *b, char *attributes)
  62. {
  63. char recipetag[RECIPETAG_STRMAX];
  64. char *rt;
  65. char envvarname[VARNAMEMAX];
  66. char *att;
  67. strcpy(recipetag, "<recipe ");
  68. rt = recipetag + 8;
  69. att = attributes;
  70. while (*att != '\0' && rt < &recipetag[RECIPETAG_STRMAX-1])
  71. {
  72. if ( *att == '$' )
  73. {
  74. int e;
  75. char *v;
  76. /* insert the value of an environent variable */
  77. att++;
  78. e = 0;
  79. do {
  80. envvarname[e++] = *att;
  81. att++;
  82. } while ( e < (VARNAMEMAX-1) && (isalnum(*att) || *att == '_'));
  83. envvarname[e] = '\0';
  84. /* DEBUG(("envvarname: %s\n", envvarname));*/
  85. v = talon_getenv(envvarname);
  86. if (v)
  87. {
  88. /* DEBUG((" value: %s\n", v)); */
  89. char *oldv=v;
  90. while (*v != '\0' && rt < &recipetag[RECIPETAG_STRMAX-1])
  91. {
  92. *rt = *v;
  93. rt++; v++;
  94. }
  95. free(oldv);
  96. }
  97. } else {
  98. *rt = *att;
  99. rt++; att++;
  100. }
  101. }
  102. char *finish = ">\n<![CDATA[\n";
  103. while (*finish != '\0' && rt < &recipetag[RECIPETAG_STRMAX-1])
  104. {
  105. *rt = *finish;
  106. rt++; finish++;
  107. }
  108. *rt = '\0';
  109. buffer_prepend(b, recipetag, strlen(recipetag));
  110. }
  111. /* read a recipe string from a temporary file.
  112. *
  113. * We only expect to do this for very long command lines on
  114. * Windows. So allocate the maximum size for CreateProcess on
  115. * Win32 (32768 bytes) and error if the file is longer than that.
  116. *
  117. */
  118. char *read_recipe_from_file(const char *filename)
  119. {
  120. FILE *fp = fopen(filename, "r");
  121. if (!fp)
  122. {
  123. error("talon: error: could not read '%s'\n", filename);
  124. return NULL;
  125. }
  126. int max_length = 32768;
  127. char *recipe = (char*)malloc(max_length);
  128. if (!recipe)
  129. {
  130. error("talon: error: could not allocate memory to read '%s'\n", filename);
  131. return NULL;
  132. }
  133. int position = 0;
  134. /* read the file one character at a time */
  135. int c;
  136. while ((c = getc(fp)) != EOF)
  137. {
  138. switch (c)
  139. {
  140. case '\r':
  141. case '\n':
  142. /* ignore newlines */
  143. break;
  144. case '"':
  145. /* double quotes have to be escaped so they aren't lost later */
  146. if (position < max_length)
  147. recipe[position++] = '\\';
  148. if (position < max_length)
  149. recipe[position++] = c;
  150. break;
  151. default:
  152. if (position < max_length)
  153. recipe[position++] = c;
  154. break;
  155. }
  156. }
  157. fclose(fp);
  158. /* add a terminating \0 */
  159. if (position < max_length)
  160. recipe[position] = '\0';
  161. else
  162. {
  163. error("talon: error: command longer than 32768 in '%s'\n", filename);
  164. return NULL;
  165. }
  166. return recipe;
  167. }
  168. int main(int argc, char *argv[])
  169. {
  170. /* find the argument to -c then strip the talon related front section */
  171. char *recipe = NULL;
  172. int talon_returncode = 0;
  173. #ifdef HAS_WINSOCK2
  174. WSADATA wsaData;
  175. WSAStartup(MAKEWORD(2,2), &wsaData);
  176. /* We ignore the result as we are only doing this to use gethostname
  177. and if that fails then leaving the host attribute blank is perfectly
  178. acceptable.
  179. */
  180. #endif
  181. #ifdef HAS_GETCOMMANDLINE
  182. char *commandline= GetCommandLine();
  183. /*
  184. * The command line should be either,
  185. * talon -c "some shell commands"
  186. * or
  187. * talon shell_script_file
  188. *
  189. * talon could be an absolute path and may have a .exe extension.
  190. */
  191. recipe = chompCommand(commandline);
  192. if (recipe)
  193. {
  194. /* there was a -c so extract the quoted commands */
  195. int recipelen = strlen(recipe);
  196. if (recipelen > 0 && recipe[recipelen - 1] == '"')
  197. recipe[recipelen - 1] = '\0'; /* remove trailing quote */
  198. }
  199. else
  200. {
  201. /* there was no -c so extract the argument as a filename */
  202. recipe = strstr(commandline, "talon");
  203. if (recipe)
  204. {
  205. /* find the first space */
  206. while (!isspace(*recipe) && *recipe != '\0')
  207. recipe++;
  208. /* skip past the spaces */
  209. while (isspace(*recipe))
  210. recipe++;
  211. recipe = read_recipe_from_file(recipe);
  212. if (!recipe)
  213. {
  214. error("talon: error: bad script file in shell call '%s'\n", commandline);
  215. return 1;
  216. }
  217. }
  218. else
  219. {
  220. error("talon: error: no 'talon' in shell call '%s'\n", commandline);
  221. return 1;
  222. }
  223. }
  224. #else
  225. /*
  226. * The command line should be either,
  227. * talon -c "some shell commands"
  228. * or
  229. * talon shell_script_file
  230. *
  231. * talon could be an absolute path and may have a .exe extension.
  232. */
  233. switch (argc)
  234. {
  235. case 2:
  236. recipe = read_recipe_from_file(argv[1]);
  237. break;
  238. case 3:
  239. if (strcmp("-c", argv[1]) != 0)
  240. {
  241. error("talon: error: %s\n", "usage is 'talon -c command' or 'talon script_filename'");
  242. return 1;
  243. }
  244. recipe = argv[2];
  245. break;
  246. default:
  247. error("talon: error: %s\n", "usage is 'talon -c command' or 'talon script_filename'");
  248. return 1;
  249. }
  250. #endif
  251. /* did we get a recipe at all? */
  252. if (!recipe)
  253. {
  254. error("talon: error: %s", "no recipe supplied to the shell.\n");
  255. return 1;
  256. }
  257. /* remove any leading white space on the recipe */
  258. while (isspace(*recipe))
  259. recipe++;
  260. /* turn debugging on? */
  261. char *debugstr=talon_getenv("TALON_DEBUG");
  262. if (debugstr)
  263. {
  264. loglevel=LOGDEBUG;
  265. free(debugstr); debugstr=NULL;
  266. }
  267. DEBUG(("talon: recipe: %s\n", recipe));
  268. /* Make sure that the agent's hostname can be put into the host attribute */
  269. char hostname[HOSTNAME_MAX];
  270. int hostresult=0;
  271. hostresult = gethostname(hostname, HOSTNAME_MAX-1);
  272. if (0 != hostresult)
  273. {
  274. DEBUG(("talon: failed to get hostname: %d\n", hostresult));
  275. hostname[0] = '\0';
  276. }
  277. talon_setenv("HOSTNAME", hostname);
  278. DEBUG(("talon: setenv: hostname: %s\n", hostname));
  279. char varname[VARNAMEMAX];
  280. char varval[VARVALMAX];
  281. int dotagging = 0;
  282. int force_descramble_off = 0;
  283. char *rp = recipe;
  284. if (*rp == TALONDELIMITER) {
  285. dotagging = 1;
  286. /* there are some talon-specific settings
  287. * in the command which must be stripped */
  288. rp++;
  289. char *out = varname;
  290. char *stopout = varname + VARNAMEMAX - 1;
  291. DEBUG(("talon: parameters found\n"));
  292. while (*rp != '\0')
  293. {
  294. switch (*rp) {
  295. case '=':
  296. *out = '\0';
  297. DEBUG(("talon: varname: %s\n",varname));
  298. out = varval;
  299. stopout = varval + VARVALMAX - 1;
  300. break;
  301. case ';':
  302. *out = '\0';
  303. DEBUG(("talon: varval: %s\n",varval));
  304. talon_setenv(varname, varval);
  305. out = varname;
  306. stopout = varname + VARNAMEMAX - 1;
  307. break;
  308. default:
  309. *out = *rp;
  310. if (out < stopout)
  311. out++;
  312. break;
  313. }
  314. if (*rp == TALONDELIMITER)
  315. {
  316. rp++;
  317. break;
  318. }
  319. rp++;
  320. }
  321. } else {
  322. /* This is probably a $(shell) statement
  323. * in make so no descrambling needed and
  324. * tags are definitely not wanted as they
  325. * would corrupt the expected output*/
  326. force_descramble_off = 1;
  327. }
  328. /* Now take settings from the environment (having potentially modified it) */
  329. if (talon_getenv("TALON_DEBUG"))
  330. loglevel=LOGDEBUG;
  331. int enverrors = 0;
  332. char *shell = talon_getenv("TALON_SHELL");
  333. if (!shell)
  334. {
  335. error("error: %s", "TALON_SHELL not set in environment\n");
  336. enverrors++;
  337. }
  338. int timeout = -1;
  339. char *timeout_str = talon_getenv("TALON_TIMEOUT");
  340. if (timeout_str)
  341. {
  342. timeout = atoi(timeout_str);
  343. free(timeout_str); timeout_str = NULL;
  344. }
  345. char *buildid = talon_getenv("TALON_BUILDID");
  346. if (!buildid)
  347. {
  348. error("error: %s", "TALON_BUILDID not set in environment\n");
  349. enverrors++;
  350. }
  351. char *attributes = talon_getenv("TALON_RECIPEATTRIBUTES");
  352. if (!attributes)
  353. {
  354. error("error: %s", "TALON_RECIPEATTRIBUTES not set in environment\n");
  355. enverrors++;
  356. }
  357. int max_retries = 0;
  358. char *retries_str = talon_getenv("TALON_RETRIES");
  359. if (retries_str)
  360. {
  361. max_retries = atoi(retries_str);
  362. free(retries_str); retries_str = NULL;
  363. }
  364. int descramble = 0;
  365. if (! force_descramble_off )
  366. {
  367. char *descramblestr = talon_getenv("TALON_DESCRAMBLE");
  368. if (descramblestr)
  369. {
  370. if (*descramblestr == '0')
  371. descramble = 0;
  372. else
  373. descramble = 1;
  374. free(descramblestr); descramblestr = NULL;
  375. }
  376. }
  377. /* check command line lengths if a maximum is supplied */
  378. int shell_cl_max = 0;
  379. char *shell_cl_max_str = talon_getenv("TALON_SHELL_CL_MAX");
  380. if (shell_cl_max_str)
  381. {
  382. shell_cl_max = atoi(shell_cl_max_str);
  383. free(shell_cl_max_str); shell_cl_max_str = NULL;
  384. }
  385. /* Talon can look in a flags variable to alter its behaviour */
  386. int force_success = 0;
  387. char *flags_str = talon_getenv("TALON_FLAGS");
  388. if (flags_str)
  389. {
  390. int c;
  391. for (c=0; flags_str[c] !=0; c++)
  392. flags_str[c] = tolower(flags_str[c]);
  393. if (strstr(flags_str, "forcesuccess"))
  394. force_success = 1;
  395. /* don't put <recipe> or <CDATA<[[ tags around the output. e.g. if it's XML already*/
  396. if (strstr(flags_str, "rawoutput"))
  397. {
  398. dotagging = 0;
  399. }
  400. free(flags_str); flags_str = NULL;
  401. }
  402. /* Talon subprocesses need to have the "correct" shell variable set. */
  403. talon_setenv("SHELL", shell);
  404. /* we have allowed some errors to build up so that the user
  405. * can see all of them before we stop and force the user
  406. * to fix them
  407. */
  408. if (enverrors)
  409. {
  410. return 1;
  411. }
  412. /* Run the recipe repeatedly until the retry count expires or
  413. * it succeeds.
  414. */
  415. int attempt = 0, retries = max_retries;
  416. proc *p = NULL;
  417. char *args[5];
  418. char *qrp=rp;
  419. #ifdef HAS_GETCOMMANDLINE
  420. /* re-quote the argument to -c since this helps windows deal with it */
  421. int qrpsize = strlen(rp) + 3;
  422. qrp = malloc(qrpsize);
  423. qrp[0] = '"';
  424. strcpy(&qrp[1], rp);
  425. qrp[qrpsize-2] = '"';
  426. qrp[qrpsize-1] = '\0';
  427. #endif
  428. int index = 0;
  429. args[index++] = shell;
  430. if (dotagging) /* don't do bash -x for non-tagged commands e.g. $(shell output) */
  431. args[index++] = "-x";
  432. args[index++] = "-c";
  433. args[index++] = qrp;
  434. args[index++] = NULL;
  435. /* get the semaphore ready */
  436. talon_sem.name = buildid;
  437. talon_sem.timeout = timeout;
  438. do
  439. {
  440. char talon_attempt[TALON_ATTEMPT_STRMAX];
  441. double start_time = getseconds();
  442. attempt++;
  443. snprintf(talon_attempt, TALON_ATTEMPT_STRMAX-1, "%d", attempt);
  444. talon_attempt[TALON_ATTEMPT_STRMAX - 1] = '\0';
  445. talon_setenv("TALON_ATTEMPT", talon_attempt);
  446. p = process_run(shell, args, timeout);
  447. double end_time = getseconds();
  448. if (p)
  449. {
  450. talon_returncode = p->returncode;
  451. if (dotagging)
  452. {
  453. char status[STATUS_STRMAX];
  454. char timestat[STATUS_STRMAX];
  455. char warning[WARNING_STRMAX];
  456. warning[0] = '\0';
  457. if (shell_cl_max)
  458. {
  459. int cl_actual = strlen(qrp);
  460. if (cl_actual > shell_cl_max)
  461. {
  462. snprintf(warning, WARNING_STRMAX-1, \
  463. "\n<warning>Command line length '%d' exceeds the shell limit on this system of '%d'. " \
  464. "If this recipe is a compile, try using the '.use_compilation_command_file' variant to reduce overall command line length.</warning>", \
  465. cl_actual, shell_cl_max);
  466. warning[WARNING_STRMAX-1] = '\0';
  467. }
  468. }
  469. char *flagsstr = force_success == 0 ? "" : " flags='FORCESUCCESS'";
  470. char *reasonstr = "" ;
  471. if (p->causeofdeath == PROC_TIMEOUTDEATH)
  472. reasonstr = " reason='timeout'";
  473. if (p->returncode != 0)
  474. {
  475. char *exitstr = (force_success || retries <= 0) ? "failed" : "retry";
  476. snprintf(status, STATUS_STRMAX - 1, "\n<status exit='%s' code='%d' attempt='%d'%s%s />", exitstr, p->returncode, attempt, flagsstr, reasonstr );
  477. } else {
  478. snprintf(status, STATUS_STRMAX - 1, "\n<status exit='ok' attempt='%d'%s%s />", attempt, flagsstr, reasonstr );
  479. }
  480. status[STATUS_STRMAX-1] = '\0';
  481. snprintf(timestat, STATUS_STRMAX - 1, "<time start='%.5f' elapsed='%.3f' />",start_time, end_time-start_time );
  482. timestat[STATUS_STRMAX-1] = '\0';
  483. prependattributes(p->output, attributes);
  484. buffer_append(p->output, "\n]]>", 4);
  485. buffer_append(p->output, timestat, strlen(timestat));
  486. buffer_append(p->output, status, strlen(status));
  487. buffer_append(p->output, warning, strlen(warning));
  488. buffer_append(p->output, "\n</recipe>\n", 11);
  489. }
  490. unsigned int iterator = 0;
  491. unsigned int written = 0;
  492. byteblock *bb;
  493. char sub[7] = "&#x00;";
  494. if (descramble)
  495. sema_wait(&talon_sem);
  496. while ((bb = buffer_getbytes(p->output, &iterator)))
  497. {
  498. if (bb->fill < 1)
  499. continue; /* empty buffer */
  500. if (dotagging)
  501. {
  502. /* the output is XML so we must replace any non-printable characters */
  503. char *ptr = &bb->byte0;
  504. char *end = ptr + bb->fill;
  505. char *start = ptr;
  506. while (ptr < end)
  507. {
  508. if ((*ptr < 32 || *ptr > 126) && *ptr != 9 && *ptr != 10 && *ptr != 13)
  509. {
  510. /* output any unwritten characters before this non-printable */
  511. if (ptr > start)
  512. write(STDOUT_FILENO, start, ptr - start);
  513. /* 0->&#x00; 1->&#x01; ... 255->&#xff; */
  514. sprintf(sub, "&#x%02x;", (unsigned char)*ptr);
  515. /* output the modified non-printable character */
  516. write(STDOUT_FILENO, sub, 6);
  517. start = ptr + 1;
  518. }
  519. ptr++;
  520. }
  521. if (ptr > start)
  522. write(STDOUT_FILENO, start, ptr - start);
  523. }
  524. else
  525. {
  526. /* the output isn't XML so write out the whole buffer as-is */
  527. written = write(STDOUT_FILENO, &bb->byte0, bb->fill);
  528. DEBUG(("talon: wrote %d bytes out of %d\n", written, bb->fill));
  529. }
  530. }
  531. if (descramble)
  532. sema_release(&talon_sem);
  533. if (p->returncode == 0 || force_success)
  534. {
  535. process_free(&p);
  536. break;
  537. }
  538. process_free(&p);
  539. } else {
  540. error("error: failed to run shell: %s: check the SHELL environment variable.\n", args[0]);
  541. return 1;
  542. }
  543. retries--;
  544. }
  545. while (retries >= 0);
  546. if (buildid) free(buildid); buildid = NULL;
  547. if (attributes) free(attributes); attributes = NULL;
  548. if (shell) free(shell); shell = NULL;
  549. if (force_success)
  550. return 0;
  551. else
  552. return talon_returncode;
  553. }