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