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