monotone

monotone Mtn Source Tree

Root/unix/tester-plaf.cc

1// Tester-specific platform interface glue, Unix version.
2
3#include "base.hh"
4#include "sanity.hh"
5#include "platform.hh"
6#include "tester-plaf.hh"
7
8#include <sys/stat.h>
9#include <sys/wait.h>
10#include <unistd.h>
11#include <fcntl.h>
12#include <errno.h>
13#include <signal.h>
14#include <string.h>
15#include <map>
16
17using std::string;
18using std::map;
19using std::make_pair;
20
21void make_accessible(string const & name)
22{
23 struct stat st;
24 if (stat(name.c_str(), &st) != 0)
25 {
26 const int err = errno;
27 E(false, F("stat(%s) failed: %s") % name % os_strerror(err));
28 }
29
30 mode_t new_mode = st.st_mode;
31 if (S_ISDIR(st.st_mode))
32 new_mode |= S_IEXEC;
33 new_mode |= S_IREAD | S_IWRITE;
34
35 if (chmod(name.c_str(), new_mode) != 0)
36 {
37 const int err = errno;
38 E(false, F("chmod(%s) failed: %s") % name % os_strerror(err));
39 }
40}
41
42time_t get_last_write_time(char const * name)
43{
44 struct stat st;
45 if (stat(name, &st) != 0)
46 {
47 const int err = errno;
48 E(false, F("stat(%s) failed: %s") % name % os_strerror(err));
49 }
50
51 return st.st_mtime;
52}
53
54void do_copy_file(string const & from, string const & to)
55{
56 char buf[32768];
57 int ifd, ofd;
58 ifd = open(from.c_str(), O_RDONLY);
59 const int err = errno;
60 E(ifd >= 0, F("open %s: %s") % from % os_strerror(err));
61 struct stat st;
62 st.st_mode = 0666; // sane default if fstat fails
63 fstat(ifd, &st);
64 ofd = open(to.c_str(), O_WRONLY|O_CREAT|O_EXCL, st.st_mode);
65 if (ofd < 0)
66 {
67 const int err = errno;
68 close(ifd);
69 E(false, F("open %s: %s") % to % os_strerror(err));
70 }
71
72 ssize_t nread, nwrite;
73 int ndead;
74 for (;;)
75 {
76 nread = read(ifd, buf, 32768);
77 if (nread < 0)
78 goto read_error;
79 if (nread == 0)
80 break;
81
82 nwrite = 0;
83 ndead = 0;
84 do
85 {
86 ssize_t nw = write(ofd, buf + nwrite, nread - nwrite);
87 if (nw < 0)
88 goto write_error;
89 if (nw == 0)
90 ndead++;
91 if (ndead == 4)
92 goto spinning;
93 nwrite += nw;
94 }
95 while (nwrite < nread);
96 }
97 close(ifd);
98 close(ofd);
99 return;
100
101 read_error:
102 {
103 int err = errno;
104 close(ifd);
105 close(ofd);
106 E(false, F("read error copying %s to %s: %s")
107 % from % to % os_strerror(err));
108 }
109 write_error: {
110 int err = errno;
111 close(ifd);
112 close(ofd);
113 E(false, F("write error copying %s to %s: %s")
114 % from % to % os_strerror(err));
115 }
116 spinning:
117 {
118 close(ifd);
119 close(ofd);
120 E(false, F("abandoning copy of %s to %s after four zero-length writes")
121 % from % to);
122 }
123}
124
125void set_env(char const * var, char const * val)
126{
127#if defined HAVE_SETENV
128 setenv(var, val, 1);
129#elif defined HAVE_PUTENV
130 // note: this leaks memory, but the tester is short lived so it probably
131 // doesn't matter much.
132 string * tempstr = new string(var);
133 tempstr->append("=");
134 tempstr->append(val);
135 putenv(const_cast<char *>(tempstr->c_str()));
136#else
137#error set_env needs to be ported to this platform
138#endif
139}
140
141void unset_env(char const * var)
142{
143#if defined HAVE_UNSETENV
144 unsetenv(var);
145#else
146#error unset_env needs to be ported to this platform
147#endif
148}
149
150// This function cannot fail, but the Windows version of this function
151// always returns -1 to indicate no system support for the operation.
152// Therefore the argument and return value are signed.
153int do_umask(int mask)
154{
155 return umask(mask);
156}
157
158char * make_temp_dir()
159{
160 char const * parent;
161 parent = getenv("TMPDIR");
162 if (parent == 0)
163 parent = getenv("TEMP");
164 if (parent == 0)
165 parent = getenv("TMP");
166 if (parent == 0)
167 parent = "/tmp";
168
169 char * templ = new char[strlen(parent) + sizeof "/mtXXXXXX"];
170 strcpy(templ, parent);
171 strcat(templ, "/mtXXXXXX");
172
173 char * result;
174
175 // mkdtemp is not available on all systems.
176#ifdef HAVE_MKDTEMP
177
178 result = mkdtemp(templ);
179 I(result == templ);
180 return templ;
181
182#else
183
184 // Typical use of mktemp() risks the file being created by someone else in
185 // between when the name is chosen and the file is opened. However, use
186 // of mktemp() to pick a *directory* name is safe, because mkdir() will
187 // not create a directory if anything already exists by that name - even a
188 // dangling symlink. Thus we can simply loop until we find a suitable
189 // name. There IS a very small risk that we loop endlessly, but that's
190 // under extreme conditions, and the problem is likely to really be
191 // elsewhere... as a backstop, we limit iterations to the smaller of
192 // 10000 and TMP_MAX.
193
194 unsigned int cycles = 0, limit = 10000;
195#ifdef TMP_MAX
196 if (TMP_MAX > 0 && TMP_MAX < limit)
197 limit = TMP_MAX;
198#endif
199
200 char * tmpdir = new char[strlen(templ) + 1];
201 for (;;)
202 {
203 strcpy(tmpdir, templ);
204 result = mktemp(tmpdir);
205 E(result, F("mktemp(%s) failed: %s") % tmpdir % os_strerror(errno));
206 I(result == tmpdir);
207
208 if (mkdir(tmpdir, 0700) == 0)
209 {
210 strcpy(templ, tmpdir);
211 delete [] tmpdir;
212 return templ;
213 }
214
215 E(errno == EEXIST,
216 F("mkdir(%s) failed: %s") % tmpdir % os_strerror(errno));
217
218 cycles++;
219 E(cycles < limit,
220 F("%d temporary names are all in use") % limit);
221 }
222
223#endif
224}
225
226
227bool running_as_root()
228{
229 return !geteuid();
230}
231
232// Parallel test case support.
233//
234// GNU Make's job server algorithm is described in detail at
235// <http://make.paulandlesley.org/jobserver.html>. This program
236// implements only part of the general algorithm: specifically, if
237// this program is invoked as if it were a recursive make, it will
238// participate in the job server algorithm when parallelizing its own
239// subcomponents. None of those subcomponents are themselves
240// recursive make operations. Therefore, what we do is:
241//
242// 1. The invoking make has created a pipe, and written N tokens into it.
243// We are entitled to run one job at any time, plus as many of the N
244// as we can get tokens for.
245//
246// * A token is just a one-byte character. (Empirically, GNU make
247// uses plus signs (ASCII 0x2B) for this.)
248// * All tokens are identical.
249//
250// 2. We know that this is the case because we observe, in the MAKEFLAGS
251// environment variable, a construct similar to:
252//
253// --jobserver-fds=R,W -j
254//
255// where R and W are integers specifying the read and write ends of
256// the jobserver communication pipe. If we do not observe any such
257// construct, we run in serial mode (this is actually implemented
258// by creating a pipe ourselves, not writing anything to it, and
259// proceeding as described below).
260//
261// 2a. If the file descriptors specified in the above construct are not
262// open, this means the invoking Makefile did not properly mark the
263// command running this program as a recursive make. We print a
264// diagnostic and run in serial mode.
265//
266// 3. We have a queue of jobs to be run, and a set of currently
267// running jobs (initially none). Before beginning the main loop,
268// we install a handler for SIGCHLD. The only thing this handler
269// does is close the duplicate jobserver read end (see below).
270//
271// The main loop proceeds as follows:
272//
273// a. Remove the next job to be run from the queue.
274//
275// b. Create a duplicate of the read side of the jobserver pipe, if
276// we don't already have one.
277//
278// c. Call wait() in nonblocking mode until it doesn't report any
279// more dead children. For each reported child, write a token
280// back to the jobserver pipe, unless it is the last running
281// child.
282//
283// d. If the set of currently running jobs is nonempty, read one
284// byte in blocking mode from the duplicate fd. If this returns
285// 1, proceed to step e. If it returns -1 and errno is either
286// EINTR or EBADF, go back to step b. Abort on any other return
287// and/or errno value.
288//
289// e. We have a token! Fork. In the child, close both sides of the
290// jobserver pipe, and the duplicate, and then invoke the job.
291//
292// 4. Once the queue of jobs is exhausted, the main loop terminates.
293// Subsequent code repeats step 3c until there are no more children,
294// doing so in *blocking* mode.
295//
296// The jiggery-pokery with duplicate read fds in 3b-3d is necessary to
297// close a race window. If we didn't do that and a SIGCHLD arrived
298// betwen steps c and d, a token could get lost and we could end up
299// hanging forever in the read(). See the above webpage for further
300// discussion.
301
302// Sadly, there is no getting around global variables for these. However,
303// the information in question really is process-global (file descriptors,
304// signal handlers) so it's not like we could be reentrant here anyway.
305
306static int jobsvr_read = -1;
307static int jobsvr_write = -1;
308static volatile int jobsvr_read_dup = -1;
309static int tokens_held = 0;
310
311static void sigchld(int)
312{
313 if (jobsvr_read_dup != -1)
314 {
315 close(jobsvr_read_dup);
316 jobsvr_read_dup = -1;
317 }
318}
319
320// Encapsulation of token acquisition and release. We get one token for free.
321// Note: returns true if we need to go reap children again, not if we have
322// successfully acquired a token.
323static bool acquire_token()
324{
325 if (tokens_held == 0)
326 {
327 tokens_held++;
328 return false;
329 }
330
331 char dummy;
332 int n = read(jobsvr_read_dup, &dummy, 1);
333 if (n == 1)
334 {
335 tokens_held++;
336 return false;
337 }
338 else
339 {
340 I(n == -1 && (errno == EINTR || errno == EBADF));
341 return true;
342 }
343}
344
345static void release_token()
346{
347 if (tokens_held > 1)
348 write(jobsvr_write, "+", 1);
349 I(tokens_held > 0);
350 tokens_held--;
351}
352
353// Set up the above static variables appropriately, given the arguments
354// to -j and/or --jobserver-fd on the command line and MAKEFLAGS.
355// Diagnostics generated here are exactly the same as GNU Make's.
356void prepare_for_parallel_testcases(int jobs, int jread, int jwrite)
357{
358 if ((jread != -1 || jwrite != -1)
359 && (fcntl(jread, F_GETFD) == -1 || fcntl(jwrite, F_GETFD) == -1))
360 {
361 W(F("jobserver unavailable: using -j1. Add `+' to parent make rule."));
362 if (jread != -1)
363 close(jread);
364
365 if (jwrite != -1)
366 close(jwrite);
367
368 jread = jwrite = -1;
369 }
370
371 if (jread != -1 && jwrite != -1 && jobs >= 2)
372 {
373 W(F("-jN forced in submake: disabling jobserver mode."));
374 close(jread);
375 close(jwrite);
376 jread = jwrite = -1;
377 }
378
379 if (jread == -1 && jwrite == -1)
380 {
381 int jp[2];
382 E(pipe(jp) == 0,
383 F("creating jobs pipe: %s") % os_strerror(errno));
384 jread = jp[0];
385 jwrite = jp[1];
386
387 if (jobs == -1)
388 jobs = 11; // infinity goes to 11, but no higher.
389
390 // can ignore errors; the worst case is we don't parallelize as much
391 // as was requested.
392 for (int i = 0; i < jobs-1; i++)
393 write(jwrite, "+", 1);
394 }
395
396 I(jread != -1 && jwrite != -1);
397 jobsvr_read = jread;
398 jobsvr_write = jwrite;
399}
400
401// Child side of the fork. The magic numbers in this function and
402// run_tests_in_children are meaningful to testlib.lua. They indicate a
403// number of failure scenarios in which more detailed diagnostics are not
404// possible.
405
406// gcc 4.1 doesn't like attributes on the function definition
407static NORETURN(void child(test_invoker const &,
408 string const &, string const &));
409
410// Note: to avoid horrible headaches, we do not touch fds 0-2 nor the stdio
411// streams. Child operations are expected to be coded not to do *anything*
412// with those streams. The use of _exit is intentional.
413static void child(test_invoker const & invoke, string const & tdir,
414 string const & tname)
415{
416 close(jobsvr_read);
417 close(jobsvr_write);
418 close(jobsvr_read_dup);
419
420 if (chdir(tdir.c_str()) != 0)
421 _exit(123);
422
423 _exit(invoke(tname));
424}
425
426void run_tests_in_children(test_enumerator const & next_test,
427 test_invoker const & invoke,
428 test_cleaner const & cleanup,
429 std::string const & run_dir,
430 std::string const & /*runner*/,
431 std::string const & /*testfile*/,
432 std::string const & /*firstdir*/)
433{
434 test_to_run t;
435 string testdir;
436 map<pid_t, test_to_run> children;
437
438 if (jobsvr_read_dup != -1)
439 {
440 close(jobsvr_read_dup);
441 jobsvr_read_dup = -1;
442 }
443
444 struct sigaction sa, osa;
445 sigemptyset(&sa.sa_mask);
446 sa.sa_handler = sigchld;
447 sa.sa_flags = SA_NOCLDSTOP; // deliberate non-use of SA_RESTART
448
449 E(sigaction(SIGCHLD, &sa, &osa) == 0,
450 F("setting SIGCHLD handler: %s") % os_strerror(errno));
451
452 while (next_test(t))
453 {
454 do
455 {
456 if (jobsvr_read_dup == -1)
457 jobsvr_read_dup = dup(jobsvr_read);
458
459 for (;;)
460 {
461 int status;
462 pid_t pid = waitpid(-1, &status, WNOHANG);
463 if (pid == 0)
464 break;
465 if (pid == -1)
466 {
467 if (errno == ECHILD)
468 break;
469 if (errno == EINTR)
470 continue;
471 E(false, F("waitpid failed: %s") % os_strerror(errno));
472 }
473
474 map<pid_t, test_to_run>::iterator tfin = children.find(pid);
475 I(tfin != children.end());
476 if (cleanup(tfin->second, status))
477 do_remove_recursive(run_dir + "/" + tfin->second.name);
478 children.erase(tfin);
479 release_token();
480 }
481 }
482 while (acquire_token());
483
484
485 // This must be done before we try to redirect stdout/err to a file
486 // within testdir. If we did it in the child, we would have to do it
487 // before it was safe to issue diagnostics.
488 try
489 {
490 testdir = run_dir + "/" + t.name;
491 do_remove_recursive(testdir);
492 do_mkdir(testdir);
493 }
494 catch (...)
495 {
496 cleanup(t, 121);
497 release_token();
498 continue;
499 }
500
501 // Make sure there is no pending buffered output before forking, or it
502 // may be doubled.
503 fflush(0);
504 pid_t pid = fork();
505 if (pid == 0)
506 child(invoke, testdir, t.name);
507 else if (pid == -1)
508 {
509 if (cleanup(t, 122))
510 do_remove_recursive(testdir);
511 release_token();
512 }
513 else
514 children.insert(make_pair(pid, t));
515 }
516
517 // Now wait for any unfinished children.
518 for (;;)
519 {
520 int status;
521 pid_t pid = waitpid(-1, &status, 0);
522 if (pid == 0)
523 break;
524 if (pid == -1)
525 {
526 if (errno == ECHILD)
527 break;
528 if (errno == EINTR)
529 continue;
530 E(false, F("waitpid failed: %s") % os_strerror(errno));
531 }
532
533 map<pid_t, test_to_run>::iterator tfin = children.find(pid);
534 I(tfin != children.end());
535 if (cleanup(tfin->second, status))
536 do_remove_recursive(run_dir + "/" + tfin->second.name);
537 children.erase(tfin);
538 release_token();
539 }
540
541 I(tokens_held == 0);
542 I(children.size() == 0);
543 if (jobsvr_read_dup != -1)
544 {
545 close(jobsvr_read_dup);
546 jobsvr_read_dup = -1;
547 }
548 sigaction(SIGCHLD, &osa, 0);
549}
550
551// Local Variables:
552// mode: C++
553// fill-column: 76
554// c-file-style: "gnu"
555// indent-tabs-mode: nil
556// End:
557// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status