descramble.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /*
  2. * Copyright (c) 2008-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. descramble.cpp:
  19. Description
  20. -----------
  21. "descramble" is a program that buffers standard input until end of file
  22. and then copies the buffers to the standard output in such a way that
  23. if there are many copies of descramble running, each will be given an
  24. opportunity to write the complete contents of its buffers while the
  25. others wait.
  26. Purpose
  27. -------
  28. descramble is used to ensure that output from multiple concurrent processes
  29. is not interleaved. It is useful in build systems such as GNU make with
  30. the -j switch, although it requires that makefiles are changed before it can
  31. work.
  32. Design
  33. ------
  34. This program may be built on Linux or on Windows. On both platforms the
  35. basic behavior is similar:
  36. 1) Read standard input into buffers. Allocate these dynamically
  37. so that there is no limit.
  38. 2) Wait on a named or system-wide semaphore.
  39. 3) Output all the buffers to stdout.
  40. The name of the semaphore is a parameter and should be chosen so that multiple
  41. instances of the build system (or whatever process is running many tasks with
  42. descramble) cannot block each other.
  43. Special Considerations for Linux
  44. --------------------------------
  45. A named semaphore is a file in the filesystem. It is not automatically removed
  46. when a process exits. descramble provides a "stop" parameter to be run at the
  47. "end" of the build system (or other process) that will clean away this semaphore.
  48. Special Considerations for Windows
  49. ----------------------------------
  50. The windows implementation is built with the MINGW toolchain. On windows
  51. problems have been noticed that require a fairly complex timeout capability
  52. such that descramble will not wait "forever" for output from stdin.
  53. This solution currently uses a "kill thread" that will stop descramble if
  54. it is blocked in a read on stdio for more than the timeout period.
  55. The "kill thread" attempts to print as much of the input from stdin as has
  56. been read so far. It scans this for XML characters and escapes them, finally
  57. printing its own XML-formatted error message with the escaped version of the
  58. input between <descramble> tags.
  59. */
  60. #include <stdio.h>
  61. #include <vector>
  62. #include <ctype.h>
  63. // what size chunks to allocate/read
  64. const int BufferSize = 4096;
  65. unsigned int globalreadcounter = 0;
  66. // OS specific headers
  67. #ifdef WIN32
  68. #include <windows.h>
  69. #include <tlhelp32.h>
  70. #include <fcntl.h> /* _O_BINARY */
  71. #else
  72. #include <stdlib.h>
  73. #include <fcntl.h>
  74. #include <semaphore.h>
  75. #include <sys/types.h>
  76. #include <signal.h>
  77. #include <string.h>
  78. #endif
  79. #include <unistd.h>
  80. #define DEFAULT_TIMEOUT (600000) // 10 minutes
  81. #define GOOD_OUTPUT 1
  82. #define BAD_OUTPUT 0
  83. typedef struct
  84. {
  85. char *name;
  86. #ifdef WIN32
  87. HANDLE handle;
  88. #else
  89. sem_t *handle;
  90. #endif
  91. unsigned int timeout;
  92. } sbs_semaphore;
  93. // readstate is a flag to indicate whether descramble is reading
  94. // it's standard input.
  95. // This allows the timeout thread on Windows to only cause the
  96. // process to exit if there is an overdue read operation on the
  97. // standard input.
  98. int readstate=1;
  99. int timeoutstate=0;
  100. // The output semaphore.
  101. sbs_semaphore sem;
  102. #ifdef WIN32
  103. HANDLE bufferMutex;
  104. // Make all output handling binary
  105. unsigned int _CRT_fmode = _O_BINARY;
  106. DWORD killPIDTree = 0;
  107. #else
  108. pid_t killPIDTree = 0;
  109. #endif
  110. // Where we store all the standard input.
  111. std::vector<char*> buffers;
  112. std::vector<int> bytesIn;
  113. void error(const char *reason, char *SEM_NAME)
  114. {
  115. fprintf(stderr, "<descrambler reason='%s' semaphore='%s' />\n", reason, SEM_NAME);
  116. exit(1);
  117. }
  118. #ifdef WIN32
  119. void killProcess(DWORD pid)
  120. {
  121. HANDLE proc = OpenProcess(PROCESS_TERMINATE,0,pid);
  122. if (proc)
  123. {
  124. TerminateProcess(proc, 1);
  125. //fprintf(stdout,"sent terminate to process=%d\n", pid);
  126. CloseHandle(proc);
  127. }
  128. else
  129. {
  130. //fprintf(stderr,"Can't open process=%d\n", pid);
  131. }
  132. }
  133. typedef struct {
  134. DWORD PID;
  135. DWORD PPID;
  136. } proc;
  137. void killProcessTreeRecursively(DWORD PPID, DWORD thisPID, std::vector<proc *> &processtree)
  138. {
  139. int idx;
  140. for (idx=0; idx < processtree.size(); idx++)
  141. {
  142. if (processtree[idx]->PID != thisPID && processtree[idx]->PPID == PPID)
  143. {
  144. killProcessTreeRecursively(processtree[idx]->PID, thisPID, processtree);
  145. //fprintf(stderr,"Found descendant =%d\n",processtree[idx]->PID );
  146. }
  147. }
  148. killProcess(PPID);
  149. }
  150. int killProcessTree(DWORD PPID)
  151. {
  152. HANDLE hSnapShot;
  153. DWORD thisProcID=0;
  154. BOOL ok;
  155. PROCESSENTRY32 pe;
  156. std::vector<proc *> processtree;
  157. thisProcID = GetCurrentProcessId();
  158. hSnapShot=CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
  159. // Put all this process information into an array;
  160. ok = Process32First(hSnapShot, &pe);
  161. while (ok)
  162. {
  163. if ( pe.th32ProcessID != thisProcID)
  164. {
  165. proc *p = new proc;
  166. p->PID = pe.th32ProcessID;
  167. p->PPID = pe.th32ParentProcessID;
  168. processtree.push_back(p);
  169. //fprintf(stderr,"Found process =%d\n", pe.th32ProcessID );
  170. }
  171. ok = Process32Next(hSnapShot, &pe);
  172. }
  173. killProcessTreeRecursively(PPID, thisProcID, processtree);
  174. CloseHandle(hSnapShot);
  175. //fprintf(stderr,"Ending killproc\n", PPID);
  176. return 0;
  177. }
  178. #endif
  179. void createDescrambleSemaphore(sbs_semaphore *s)
  180. {
  181. #ifdef WIN32
  182. s->handle = CreateSemaphore(NULL, 1, 1, s->name);
  183. if (s->handle)
  184. CloseHandle(s->handle);
  185. else
  186. error("unable to create semaphore", s->name);
  187. #else
  188. s->handle = sem_open(s->name, O_CREAT | O_EXCL, 0644, 1);
  189. if (s->handle == SEM_FAILED)
  190. {
  191. sem_close(s->handle);
  192. error("unable to create semaphore", s->name);
  193. }
  194. sem_close(s->handle);
  195. #endif
  196. }
  197. void destroyDescrambleSemaphore(sbs_semaphore *s)
  198. {
  199. #ifdef WIN32
  200. /* can't destroy a windows semaphore... */
  201. #else
  202. if (sem_unlink(s->name) != 0)
  203. error("unable to unlink semaphore", s->name);
  204. #endif
  205. }
  206. int waitForOutput(sbs_semaphore *s)
  207. {
  208. /* try and open the semaphore now */
  209. #ifdef WIN32
  210. s->handle = CreateSemaphore(NULL, 1, 1, s->name);
  211. if (!s->handle)
  212. error("unable to open semaphore", s->name);
  213. #else
  214. s->handle = sem_open(s->name, 0);
  215. if (s->handle == SEM_FAILED)
  216. {
  217. sem_close(s->handle);
  218. error("unable to open semaphore", s->name);
  219. }
  220. #endif
  221. /* wait for the semaphore to be free [timeout after 60 seconds] */
  222. int timedOutFlag = 0;
  223. #ifdef WIN32
  224. timedOutFlag = (WaitForSingleObject(s->handle, s->timeout) != WAIT_OBJECT_0);
  225. #else
  226. sem_wait(s->handle);
  227. #endif
  228. return timedOutFlag;
  229. }
  230. void releaseOutput(sbs_semaphore *s)
  231. {
  232. /* release the semaphore */
  233. #ifdef WIN32
  234. ReleaseSemaphore(s->handle, 1, NULL);
  235. #else
  236. sem_post(s->handle);
  237. #endif
  238. /* clean up */
  239. #ifdef WIN32
  240. CloseHandle(s->handle);
  241. #else
  242. sem_close(s->handle);
  243. #endif
  244. }
  245. void writeBuffers(int goodoutput)
  246. {
  247. /* write stdin buffers to stdout. If the output comes
  248. from a partially-complete command then make sure that there
  249. is no malformed xml inside by escaping it. */
  250. char *escaped_output=NULL;
  251. #ifdef WIN32
  252. DWORD dwWaitResult = WaitForSingleObject(
  253. bufferMutex,
  254. INFINITE);
  255. #endif
  256. for (int i = 0; i < buffers.size(); i++)
  257. {
  258. int bytes_out;
  259. char *outbuf;
  260. if (goodoutput != GOOD_OUTPUT)
  261. {
  262. if (!escaped_output)
  263. escaped_output = new char[BufferSize*4];
  264. if (!escaped_output)
  265. error("No memory for escaped outputbuffer.",sem.name);
  266. char *buf = buffers[i];
  267. bytes_out = 0;
  268. for (int idx=0; idx < bytesIn[i]; idx++)
  269. {
  270. switch (buf[idx])
  271. {
  272. case '&':
  273. escaped_output[bytes_out++]='&';
  274. escaped_output[bytes_out++]='a';
  275. escaped_output[bytes_out++]='m';
  276. escaped_output[bytes_out++]='p';
  277. escaped_output[bytes_out++]=';';
  278. break;
  279. case '<':
  280. escaped_output[bytes_out++]='&';
  281. escaped_output[bytes_out++]='l';
  282. escaped_output[bytes_out++]='t';
  283. escaped_output[bytes_out++]=';';
  284. break;
  285. case '>':
  286. escaped_output[bytes_out++]='&';
  287. escaped_output[bytes_out++]='g';
  288. escaped_output[bytes_out++]='t';
  289. escaped_output[bytes_out++]=';';
  290. break;
  291. default:
  292. if (!iscntrl(buf[idx]) || buf[idx] == '\n' || buf[idx] == '\r')
  293. escaped_output[bytes_out++]=buf[idx];
  294. break;
  295. }
  296. }
  297. outbuf = escaped_output;
  298. } else {
  299. outbuf = buffers[i];
  300. bytes_out = bytesIn[i];
  301. }
  302. fwrite(outbuf, 1, bytes_out, stdout);
  303. }
  304. #ifdef WIN32
  305. ReleaseMutex(bufferMutex);
  306. #endif
  307. if (escaped_output)
  308. delete escaped_output;
  309. fflush(stdout);
  310. }
  311. #ifdef WIN32
  312. /*
  313. A Thread that kills this process if it is "stuck" in a read operation
  314. for longer than the timeout period.
  315. There are some race conditions here that don't matter. e.g. globalreadcounter
  316. is not protected. This might result in an "unfair" timeout but we don't care
  317. because the timeout should be pretty long and anything that's even nearly
  318. a timeout deserves to be timed out.
  319. Additionally, if the timeout is so quick that this function starts before the first
  320. ever read has happened then there would be a premature timeout. This is not likely
  321. so we also dont' care - the timeout has a minimum value which is more than long
  322. enough (500msec) to deal with that.
  323. */
  324. DWORD descrambleKillerThread(void * param)
  325. {
  326. sbs_semaphore *s;
  327. unsigned int stored_globalreadcounter;
  328. s = (sbs_semaphore *)param;
  329. fflush(stderr);
  330. //fprintf(stdout, " timeout=%u sem_name='%s' \n", s->timeout, s->name);
  331. do
  332. {
  333. stored_globalreadcounter = globalreadcounter;
  334. Sleep(s->timeout);
  335. }
  336. while (globalreadcounter != stored_globalreadcounter);
  337. if (waitForOutput(s) != 0)
  338. {
  339. fprintf(stdout, "<descrambler reason='semaphore wait exceeded %ums timeout' semaphore='%s' />\n", s->timeout, s->name);
  340. ExitProcess(3);
  341. }
  342. if (readstate)
  343. {
  344. fprintf(stdout, "<descrambler reason='command output read exceeded %ums timeout' semaphore='%s'>\n", s->timeout, s->name);
  345. writeBuffers(BAD_OUTPUT);
  346. fprintf(stdout, "</descrambler>\n");
  347. fflush(stdout);
  348. if (killPIDTree != 0)
  349. killProcessTree(killPIDTree); // Make sure peers and parents all die. Nasty
  350. ExitProcess(2);
  351. }
  352. else
  353. {
  354. writeBuffers(GOOD_OUTPUT);
  355. }
  356. // Don't release the semaphore in case the main thread
  357. // gets it and tries to write the output.
  358. // Input process finished while we were waiting
  359. // for the semaphore so a false alarm.
  360. fflush(stdout);
  361. ExitProcess(0);
  362. }
  363. #endif
  364. int main(int argc, char *argv[])
  365. {
  366. char usage[]="usage: %s [-t timeout_millisecs] [ -k kill_PID_tree_on_fail ] buildID [start | stop]\nwhere timeout_millisecs >= 500\n";
  367. int opt_res;
  368. char options[]="t:k:";
  369. sem.timeout = DEFAULT_TIMEOUT;
  370. opt_res = getopt(argc, argv, options);
  371. while (opt_res != -1 )
  372. {
  373. switch (opt_res)
  374. {
  375. case 'k':
  376. if (!optarg)
  377. {
  378. fprintf(stderr, "PID argument required for 'kill PID tree on fail' option\n");
  379. fprintf(stderr, usage, argv[0]);
  380. exit(1);
  381. }
  382. killPIDTree = atol(optarg);
  383. if (killPIDTree == 0)
  384. {
  385. fprintf(stderr, usage, argv[0]);
  386. fprintf(stderr, "kill PID tree on fail must be > 0: %u\n", killPIDTree);
  387. exit(1);
  388. }
  389. break;
  390. case 't':
  391. if (!optarg)
  392. {
  393. fprintf(stderr, "timeout argument required for timeout option\n");
  394. fprintf(stderr, usage, argv[0]);
  395. exit(1);
  396. }
  397. sem.timeout = atoi(optarg);
  398. if (sem.timeout < 500)
  399. {
  400. fprintf(stderr, usage, argv[0]);
  401. fprintf(stderr, "timeout was too low: %u\n", sem.timeout);
  402. exit(1);
  403. }
  404. break;
  405. case '?':
  406. default:
  407. fprintf(stderr, usage, argv[0]);
  408. fprintf(stderr, "Unknown option. %s\n", opterr);
  409. exit(1);
  410. break;
  411. }
  412. opt_res = getopt(argc, argv, options);
  413. }
  414. if (optind >= argc)
  415. {
  416. fprintf(stderr, usage, argv[0]);
  417. fprintf(stderr, "Missing buildID\n");
  418. exit(1);
  419. }
  420. sem.name = argv[optind];
  421. if (optind + 1 < argc)
  422. {
  423. optind++;
  424. if (strncmp(argv[optind], "stop",4) == 0)
  425. destroyDescrambleSemaphore(&sem);
  426. else if (strncmp(argv[optind],"start",5) == 0)
  427. createDescrambleSemaphore(&sem);
  428. else
  429. {
  430. fprintf(stderr, usage, argv[0]);
  431. fprintf(stderr, "Unknown argument:: %s\n", argv[optind]);
  432. exit(1);
  433. }
  434. exit(0);
  435. }
  436. #ifdef WIN32
  437. HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
  438. bufferMutex = CreateMutex(NULL, FALSE, NULL);
  439. /*
  440. HANDLE WINAPI CreateThread(
  441. __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
  442. __in SIZE_T dwStackSize,
  443. __in LPTHREAD_START_ROUTINE lpStartAddress,
  444. __in_opt LPVOID lpParameter,
  445. __in DWORD dwCreationFlags,
  446. __out_opt LPDWORD lpThreadId
  447. ); */
  448. DWORD killerThreadId;
  449. HANDLE hKillerThread;
  450. hKillerThread = CreateThread(NULL, 4096, (LPTHREAD_START_ROUTINE) descrambleKillerThread, (void*)&sem, 0, &killerThreadId);
  451. #endif
  452. /* read all of my stdin into buffers */
  453. int bytesRead = 0;
  454. int bufferIndex = 0;
  455. do
  456. {
  457. char *buffer = new char[BufferSize];
  458. if (buffer == NULL)
  459. error("not enough memory for buffer", sem.name);
  460. // Add an empty buffer in advance so that if there is a timeout
  461. // the partial command result can be gathered.
  462. #ifdef WIN32
  463. DWORD dwWaitResult = WaitForSingleObject(
  464. bufferMutex,
  465. INFINITE);
  466. #endif
  467. buffers.push_back(buffer);
  468. bytesIn.push_back(0);
  469. int *counter = &bytesIn[bufferIndex];
  470. #ifdef WIN32
  471. ReleaseMutex(bufferMutex);
  472. #endif
  473. // Empty buffer added.
  474. char c = fgetc(stdin);
  475. //fprintf(stderr, "counter %d buffersize %d\n", *counter, BufferSize);
  476. do
  477. {
  478. if (c == EOF)
  479. break;
  480. // escape unprintable characters that might make logs unparsable.
  481. if (iscntrl(c) && !isspace(c))
  482. c = '_';
  483. buffer[*counter] = c;
  484. *counter += 1;
  485. if (*counter >= BufferSize)
  486. break;
  487. c = fgetc(stdin);
  488. globalreadcounter = ++globalreadcounter % 65535*4;
  489. }
  490. while (c != EOF);
  491. //fprintf(stderr, "## %d bytesin %d\n", bufferIndex, *counter);
  492. bufferIndex++;
  493. }
  494. while (!feof(stdin) && !timeoutstate);
  495. readstate = 0; // Tell the killerthread that it can back off.
  496. int timedout;
  497. timedout = waitForOutput(&sem);
  498. if (timedout)
  499. error("timed out waiting for semaphore", sem.name);
  500. else
  501. {
  502. writeBuffers(1);
  503. }
  504. releaseOutput(&sem);
  505. /* let the OS free the buffer memory */
  506. exit(0);
  507. }