1290001Sglebius/* 2290001Sglebius * work_fork.c - fork implementation for blocking worker child. 3290001Sglebius */ 4290001Sglebius#include <config.h> 5290001Sglebius#include "ntp_workimpl.h" 6290001Sglebius 7290001Sglebius#ifdef WORK_FORK 8290001Sglebius#include <stdio.h> 9290001Sglebius#include <ctype.h> 10290001Sglebius#include <signal.h> 11290001Sglebius#include <sys/wait.h> 12290001Sglebius 13290001Sglebius#include "iosignal.h" 14290001Sglebius#include "ntp_stdlib.h" 15290001Sglebius#include "ntp_malloc.h" 16290001Sglebius#include "ntp_syslog.h" 17290001Sglebius#include "ntpd.h" 18290001Sglebius#include "ntp_io.h" 19290001Sglebius#include "ntp_assert.h" 20290001Sglebius#include "ntp_unixtime.h" 21290001Sglebius#include "ntp_worker.h" 22290001Sglebius 23290001Sglebius/* === variables === */ 24290001Sglebius int worker_process; 25290001Sglebius addremove_io_fd_func addremove_io_fd; 26290001Sglebiusstatic volatile int worker_sighup_received; 27290001Sglebius 28290001Sglebius/* === function prototypes === */ 29290001Sglebiusstatic void fork_blocking_child(blocking_child *); 30290001Sglebiusstatic RETSIGTYPE worker_sighup(int); 31290001Sglebiusstatic void send_worker_home_atexit(void); 32290001Sglebiusstatic void cleanup_after_child(blocking_child *); 33290001Sglebius 34290001Sglebius/* === functions === */ 35290001Sglebius/* 36290001Sglebius * exit_worker() 37290001Sglebius * 38290001Sglebius * On some systems _exit() is preferred to exit() for forked children. 39290001Sglebius * For example, http://netbsd.gw.com/cgi-bin/man-cgi?fork++NetBSD-5.0 40290001Sglebius * recommends _exit() to avoid double-flushing C runtime stream buffers 41290001Sglebius * and also to avoid calling the parent's atexit() routines in the 42290001Sglebius * child. On those systems WORKER_CHILD_EXIT is _exit. Since _exit 43290001Sglebius * bypasses CRT cleanup, fflush() files we know might have output 44290001Sglebius * buffered. 45290001Sglebius */ 46290001Sglebiusvoid 47290001Sglebiusexit_worker( 48290001Sglebius int exitcode 49290001Sglebius ) 50290001Sglebius{ 51290001Sglebius if (syslog_file != NULL) 52290001Sglebius fflush(syslog_file); 53290001Sglebius fflush(stdout); 54290001Sglebius fflush(stderr); 55290001Sglebius WORKER_CHILD_EXIT (exitcode); /* space before ( required */ 56290001Sglebius} 57290001Sglebius 58290001Sglebius 59290001Sglebiusstatic RETSIGTYPE 60290001Sglebiusworker_sighup( 61290001Sglebius int sig 62290001Sglebius ) 63290001Sglebius{ 64290001Sglebius if (SIGHUP == sig) 65290001Sglebius worker_sighup_received = 1; 66290001Sglebius} 67290001Sglebius 68290001Sglebius 69290001Sglebiusint 70290001Sglebiusworker_sleep( 71290001Sglebius blocking_child * c, 72290001Sglebius time_t seconds 73290001Sglebius ) 74290001Sglebius{ 75290001Sglebius u_int sleep_remain; 76290001Sglebius 77290001Sglebius sleep_remain = (u_int)seconds; 78290001Sglebius do { 79290001Sglebius if (!worker_sighup_received) 80290001Sglebius sleep_remain = sleep(sleep_remain); 81290001Sglebius if (worker_sighup_received) { 82290001Sglebius TRACE(1, ("worker SIGHUP with %us left to sleep", 83290001Sglebius sleep_remain)); 84290001Sglebius worker_sighup_received = 0; 85290001Sglebius return -1; 86290001Sglebius } 87290001Sglebius } while (sleep_remain); 88290001Sglebius 89290001Sglebius return 0; 90290001Sglebius} 91290001Sglebius 92290001Sglebius 93290001Sglebiusvoid 94290001Sglebiusinterrupt_worker_sleep(void) 95290001Sglebius{ 96290001Sglebius u_int idx; 97290001Sglebius blocking_child * c; 98290001Sglebius int rc; 99290001Sglebius 100290001Sglebius for (idx = 0; idx < blocking_children_alloc; idx++) { 101290001Sglebius c = blocking_children[idx]; 102290001Sglebius 103290001Sglebius if (NULL == c || c->reusable == TRUE) 104290001Sglebius continue; 105290001Sglebius 106290001Sglebius rc = kill(c->pid, SIGHUP); 107290001Sglebius if (rc < 0) 108290001Sglebius msyslog(LOG_ERR, 109290001Sglebius "Unable to signal HUP to wake child pid %d: %m", 110290001Sglebius c->pid); 111290001Sglebius } 112290001Sglebius} 113290001Sglebius 114290001Sglebius 115290001Sglebius/* 116290001Sglebius * harvest_child_status() runs in the parent. 117310419Sdelphij * 118310419Sdelphij * Note the error handling -- this is an interaction with SIGCHLD. 119310419Sdelphij * SIG_IGN on SIGCHLD on some OSes means do not wait but reap 120310419Sdelphij * automatically. Since we're not really interested in the result code, 121310419Sdelphij * we simply ignore the error. 122290001Sglebius */ 123290001Sglebiusstatic void 124290001Sglebiusharvest_child_status( 125290001Sglebius blocking_child * c 126290001Sglebius ) 127290001Sglebius{ 128310419Sdelphij if (c->pid) { 129290001Sglebius /* Wait on the child so it can finish terminating */ 130290001Sglebius if (waitpid(c->pid, NULL, 0) == c->pid) 131290001Sglebius TRACE(4, ("harvested child %d\n", c->pid)); 132310419Sdelphij else if (errno != ECHILD) 133310419Sdelphij msyslog(LOG_ERR, "error waiting on child %d: %m", c->pid); 134310419Sdelphij c->pid = 0; 135290001Sglebius } 136290001Sglebius} 137290001Sglebius 138290001Sglebius/* 139290001Sglebius * req_child_exit() runs in the parent. 140290001Sglebius */ 141290001Sglebiusint 142290001Sglebiusreq_child_exit( 143290001Sglebius blocking_child * c 144290001Sglebius ) 145290001Sglebius{ 146290001Sglebius if (-1 != c->req_write_pipe) { 147290001Sglebius close(c->req_write_pipe); 148290001Sglebius c->req_write_pipe = -1; 149290001Sglebius return 0; 150290001Sglebius } 151290001Sglebius /* Closing the pipe forces the child to exit */ 152290001Sglebius harvest_child_status(c); 153290001Sglebius return -1; 154290001Sglebius} 155290001Sglebius 156290001Sglebius 157290001Sglebius/* 158290001Sglebius * cleanup_after_child() runs in parent. 159290001Sglebius */ 160290001Sglebiusstatic void 161290001Sglebiuscleanup_after_child( 162290001Sglebius blocking_child * c 163290001Sglebius ) 164290001Sglebius{ 165290001Sglebius harvest_child_status(c); 166290001Sglebius if (-1 != c->resp_read_pipe) { 167290001Sglebius (*addremove_io_fd)(c->resp_read_pipe, c->ispipe, TRUE); 168290001Sglebius close(c->resp_read_pipe); 169290001Sglebius c->resp_read_pipe = -1; 170290001Sglebius } 171290001Sglebius c->resp_read_ctx = NULL; 172290001Sglebius DEBUG_INSIST(-1 == c->req_read_pipe); 173290001Sglebius DEBUG_INSIST(-1 == c->resp_write_pipe); 174290001Sglebius c->reusable = TRUE; 175290001Sglebius} 176290001Sglebius 177290001Sglebius 178290001Sglebiusstatic void 179290001Sglebiussend_worker_home_atexit(void) 180290001Sglebius{ 181290001Sglebius u_int idx; 182290001Sglebius blocking_child * c; 183290001Sglebius 184290001Sglebius if (worker_process) 185290001Sglebius return; 186290001Sglebius 187290001Sglebius for (idx = 0; idx < blocking_children_alloc; idx++) { 188290001Sglebius c = blocking_children[idx]; 189290001Sglebius if (NULL == c) 190290001Sglebius continue; 191290001Sglebius req_child_exit(c); 192290001Sglebius } 193290001Sglebius} 194290001Sglebius 195290001Sglebius 196290001Sglebiusint 197290001Sglebiussend_blocking_req_internal( 198290001Sglebius blocking_child * c, 199290001Sglebius blocking_pipe_header * hdr, 200290001Sglebius void * data 201290001Sglebius ) 202290001Sglebius{ 203290001Sglebius int octets; 204290001Sglebius int rc; 205290001Sglebius 206290001Sglebius DEBUG_REQUIRE(hdr != NULL); 207290001Sglebius DEBUG_REQUIRE(data != NULL); 208290001Sglebius DEBUG_REQUIRE(BLOCKING_REQ_MAGIC == hdr->magic_sig); 209290001Sglebius 210290001Sglebius if (-1 == c->req_write_pipe) { 211290001Sglebius fork_blocking_child(c); 212290001Sglebius DEBUG_INSIST(-1 != c->req_write_pipe); 213290001Sglebius } 214290001Sglebius 215290001Sglebius octets = sizeof(*hdr); 216290001Sglebius rc = write(c->req_write_pipe, hdr, octets); 217290001Sglebius 218290001Sglebius if (rc == octets) { 219290001Sglebius octets = hdr->octets - sizeof(*hdr); 220290001Sglebius rc = write(c->req_write_pipe, data, octets); 221290001Sglebius 222290001Sglebius if (rc == octets) 223290001Sglebius return 0; 224290001Sglebius } 225290001Sglebius 226290001Sglebius if (rc < 0) 227290001Sglebius msyslog(LOG_ERR, 228290001Sglebius "send_blocking_req_internal: pipe write: %m"); 229290001Sglebius else 230290001Sglebius msyslog(LOG_ERR, 231290001Sglebius "send_blocking_req_internal: short write %d of %d", 232290001Sglebius rc, octets); 233290001Sglebius 234290001Sglebius /* Fatal error. Clean up the child process. */ 235290001Sglebius req_child_exit(c); 236290001Sglebius exit(1); /* otherwise would be return -1 */ 237290001Sglebius} 238290001Sglebius 239290001Sglebius 240290001Sglebiusblocking_pipe_header * 241290001Sglebiusreceive_blocking_req_internal( 242290001Sglebius blocking_child * c 243290001Sglebius ) 244290001Sglebius{ 245290001Sglebius blocking_pipe_header hdr; 246290001Sglebius blocking_pipe_header * req; 247290001Sglebius int rc; 248290001Sglebius long octets; 249290001Sglebius 250290001Sglebius DEBUG_REQUIRE(-1 != c->req_read_pipe); 251290001Sglebius 252290001Sglebius req = NULL; 253290001Sglebius 254290001Sglebius do { 255290001Sglebius rc = read(c->req_read_pipe, &hdr, sizeof(hdr)); 256290001Sglebius } while (rc < 0 && EINTR == errno); 257290001Sglebius 258290001Sglebius if (rc < 0) { 259290001Sglebius msyslog(LOG_ERR, 260290001Sglebius "receive_blocking_req_internal: pipe read %m"); 261290001Sglebius } else if (0 == rc) { 262290001Sglebius TRACE(4, ("parent closed request pipe, child %d terminating\n", 263290001Sglebius c->pid)); 264290001Sglebius } else if (rc != sizeof(hdr)) { 265290001Sglebius msyslog(LOG_ERR, 266290001Sglebius "receive_blocking_req_internal: short header read %d of %lu", 267290001Sglebius rc, (u_long)sizeof(hdr)); 268290001Sglebius } else { 269290001Sglebius INSIST(sizeof(hdr) < hdr.octets && hdr.octets < 4 * 1024); 270290001Sglebius req = emalloc(hdr.octets); 271290001Sglebius memcpy(req, &hdr, sizeof(*req)); 272290001Sglebius octets = hdr.octets - sizeof(hdr); 273290001Sglebius rc = read(c->req_read_pipe, (char *)req + sizeof(*req), 274290001Sglebius octets); 275290001Sglebius 276290001Sglebius if (rc < 0) 277290001Sglebius msyslog(LOG_ERR, 278290001Sglebius "receive_blocking_req_internal: pipe data read %m"); 279290001Sglebius else if (rc != octets) 280290001Sglebius msyslog(LOG_ERR, 281290001Sglebius "receive_blocking_req_internal: short read %d of %ld", 282290001Sglebius rc, octets); 283290001Sglebius else if (BLOCKING_REQ_MAGIC != req->magic_sig) 284290001Sglebius msyslog(LOG_ERR, 285290001Sglebius "receive_blocking_req_internal: packet header mismatch (0x%x)", 286290001Sglebius req->magic_sig); 287290001Sglebius else 288290001Sglebius return req; 289290001Sglebius } 290290001Sglebius 291290001Sglebius if (req != NULL) 292290001Sglebius free(req); 293290001Sglebius 294290001Sglebius return NULL; 295290001Sglebius} 296290001Sglebius 297290001Sglebius 298290001Sglebiusint 299290001Sglebiussend_blocking_resp_internal( 300290001Sglebius blocking_child * c, 301290001Sglebius blocking_pipe_header * resp 302290001Sglebius ) 303290001Sglebius{ 304290001Sglebius long octets; 305290001Sglebius int rc; 306290001Sglebius 307290001Sglebius DEBUG_REQUIRE(-1 != c->resp_write_pipe); 308290001Sglebius 309290001Sglebius octets = resp->octets; 310290001Sglebius rc = write(c->resp_write_pipe, resp, octets); 311290001Sglebius free(resp); 312290001Sglebius 313290001Sglebius if (octets == rc) 314290001Sglebius return 0; 315290001Sglebius 316290001Sglebius if (rc < 0) 317290001Sglebius TRACE(1, ("send_blocking_resp_internal: pipe write %m\n")); 318290001Sglebius else 319290001Sglebius TRACE(1, ("send_blocking_resp_internal: short write %d of %ld\n", 320290001Sglebius rc, octets)); 321290001Sglebius 322290001Sglebius return -1; 323290001Sglebius} 324290001Sglebius 325290001Sglebius 326290001Sglebiusblocking_pipe_header * 327290001Sglebiusreceive_blocking_resp_internal( 328290001Sglebius blocking_child * c 329290001Sglebius ) 330290001Sglebius{ 331290001Sglebius blocking_pipe_header hdr; 332290001Sglebius blocking_pipe_header * resp; 333290001Sglebius int rc; 334290001Sglebius long octets; 335290001Sglebius 336290001Sglebius DEBUG_REQUIRE(c->resp_read_pipe != -1); 337290001Sglebius 338290001Sglebius resp = NULL; 339290001Sglebius rc = read(c->resp_read_pipe, &hdr, sizeof(hdr)); 340290001Sglebius 341290001Sglebius if (rc < 0) { 342290001Sglebius TRACE(1, ("receive_blocking_resp_internal: pipe read %m\n")); 343290001Sglebius } else if (0 == rc) { 344290001Sglebius /* this is the normal child exited indication */ 345290001Sglebius } else if (rc != sizeof(hdr)) { 346290001Sglebius TRACE(1, ("receive_blocking_resp_internal: short header read %d of %lu\n", 347290001Sglebius rc, (u_long)sizeof(hdr))); 348290001Sglebius } else if (BLOCKING_RESP_MAGIC != hdr.magic_sig) { 349290001Sglebius TRACE(1, ("receive_blocking_resp_internal: header mismatch (0x%x)\n", 350290001Sglebius hdr.magic_sig)); 351290001Sglebius } else { 352290001Sglebius INSIST(sizeof(hdr) < hdr.octets && 353290001Sglebius hdr.octets < 16 * 1024); 354290001Sglebius resp = emalloc(hdr.octets); 355290001Sglebius memcpy(resp, &hdr, sizeof(*resp)); 356290001Sglebius octets = hdr.octets - sizeof(hdr); 357290001Sglebius rc = read(c->resp_read_pipe, 358290001Sglebius (char *)resp + sizeof(*resp), 359290001Sglebius octets); 360290001Sglebius 361290001Sglebius if (rc < 0) 362290001Sglebius TRACE(1, ("receive_blocking_resp_internal: pipe data read %m\n")); 363290001Sglebius else if (rc < octets) 364290001Sglebius TRACE(1, ("receive_blocking_resp_internal: short read %d of %ld\n", 365290001Sglebius rc, octets)); 366290001Sglebius else 367290001Sglebius return resp; 368290001Sglebius } 369290001Sglebius 370290001Sglebius cleanup_after_child(c); 371290001Sglebius 372290001Sglebius if (resp != NULL) 373290001Sglebius free(resp); 374290001Sglebius 375290001Sglebius return NULL; 376290001Sglebius} 377290001Sglebius 378290001Sglebius 379290001Sglebius#if defined(HAVE_DROPROOT) && defined(WORK_FORK) 380290001Sglebiusvoid 381290001Sglebiusfork_deferred_worker(void) 382290001Sglebius{ 383290001Sglebius u_int idx; 384290001Sglebius blocking_child * c; 385290001Sglebius 386290001Sglebius REQUIRE(droproot && root_dropped); 387290001Sglebius 388290001Sglebius for (idx = 0; idx < blocking_children_alloc; idx++) { 389290001Sglebius c = blocking_children[idx]; 390290001Sglebius if (NULL == c) 391290001Sglebius continue; 392290001Sglebius if (-1 != c->req_write_pipe && 0 == c->pid) 393290001Sglebius fork_blocking_child(c); 394290001Sglebius } 395290001Sglebius} 396290001Sglebius#endif 397290001Sglebius 398290001Sglebius 399290001Sglebiusstatic void 400290001Sglebiusfork_blocking_child( 401290001Sglebius blocking_child * c 402290001Sglebius ) 403290001Sglebius{ 404290001Sglebius static int atexit_installed; 405290001Sglebius static int blocking_pipes[4] = { -1, -1, -1, -1 }; 406290001Sglebius int rc; 407290001Sglebius int was_pipe; 408290001Sglebius int is_pipe; 409290001Sglebius int saved_errno = 0; 410290001Sglebius int childpid; 411290001Sglebius int keep_fd; 412290001Sglebius int fd; 413290001Sglebius 414290001Sglebius /* 415290001Sglebius * parent and child communicate via a pair of pipes. 416290001Sglebius * 417290001Sglebius * 0 child read request 418290001Sglebius * 1 parent write request 419290001Sglebius * 2 parent read response 420290001Sglebius * 3 child write response 421290001Sglebius */ 422290001Sglebius if (-1 == c->req_write_pipe) { 423290001Sglebius rc = pipe_socketpair(&blocking_pipes[0], &was_pipe); 424290001Sglebius if (0 != rc) { 425290001Sglebius saved_errno = errno; 426290001Sglebius } else { 427290001Sglebius rc = pipe_socketpair(&blocking_pipes[2], &is_pipe); 428290001Sglebius if (0 != rc) { 429290001Sglebius saved_errno = errno; 430290001Sglebius close(blocking_pipes[0]); 431290001Sglebius close(blocking_pipes[1]); 432290001Sglebius } else { 433290001Sglebius INSIST(was_pipe == is_pipe); 434290001Sglebius } 435290001Sglebius } 436290001Sglebius if (0 != rc) { 437290001Sglebius errno = saved_errno; 438290001Sglebius msyslog(LOG_ERR, "unable to create worker pipes: %m"); 439290001Sglebius exit(1); 440290001Sglebius } 441290001Sglebius 442290001Sglebius /* 443290001Sglebius * Move the descriptors the parent will keep open out of the 444290001Sglebius * low descriptors preferred by C runtime buffered FILE *. 445290001Sglebius */ 446290001Sglebius c->req_write_pipe = move_fd(blocking_pipes[1]); 447290001Sglebius c->resp_read_pipe = move_fd(blocking_pipes[2]); 448290001Sglebius /* 449290001Sglebius * wake any worker child on orderly shutdown of the 450290001Sglebius * daemon so that it can notice the broken pipes and 451290001Sglebius * go away promptly. 452290001Sglebius */ 453290001Sglebius if (!atexit_installed) { 454290001Sglebius atexit(&send_worker_home_atexit); 455290001Sglebius atexit_installed = TRUE; 456290001Sglebius } 457290001Sglebius } 458290001Sglebius 459298770Sdelphij#if defined(HAVE_DROPROOT) && !defined(NEED_EARLY_FORK) 460290001Sglebius /* defer the fork until after root is dropped */ 461290001Sglebius if (droproot && !root_dropped) 462290001Sglebius return; 463290001Sglebius#endif 464290001Sglebius if (syslog_file != NULL) 465290001Sglebius fflush(syslog_file); 466290001Sglebius fflush(stdout); 467290001Sglebius fflush(stderr); 468290001Sglebius 469310419Sdelphij /* [BUG 3050] setting SIGCHLD to SIG_IGN likely causes unwanted 470310419Sdelphij * or undefined effects. We don't do it and leave SIGCHLD alone. 471310419Sdelphij */ 472310419Sdelphij /* signal_no_reset(SIGCHLD, SIG_IGN); */ 473290001Sglebius 474290001Sglebius childpid = fork(); 475290001Sglebius if (-1 == childpid) { 476290001Sglebius msyslog(LOG_ERR, "unable to fork worker: %m"); 477290001Sglebius exit(1); 478290001Sglebius } 479290001Sglebius 480290001Sglebius if (childpid) { 481290001Sglebius /* this is the parent */ 482290001Sglebius TRACE(1, ("forked worker child (pid %d)\n", childpid)); 483290001Sglebius c->pid = childpid; 484290001Sglebius c->ispipe = is_pipe; 485290001Sglebius 486290001Sglebius /* close the child's pipe descriptors. */ 487290001Sglebius close(blocking_pipes[0]); 488290001Sglebius close(blocking_pipes[3]); 489290001Sglebius 490290001Sglebius memset(blocking_pipes, -1, sizeof(blocking_pipes)); 491290001Sglebius 492290001Sglebius /* wire into I/O loop */ 493290001Sglebius (*addremove_io_fd)(c->resp_read_pipe, is_pipe, FALSE); 494290001Sglebius 495290001Sglebius return; /* parent returns */ 496290001Sglebius } 497290001Sglebius 498290001Sglebius /* 499290001Sglebius * The parent gets the child pid as the return value of fork(). 500290001Sglebius * The child must work for it. 501290001Sglebius */ 502290001Sglebius c->pid = getpid(); 503290001Sglebius worker_process = TRUE; 504290001Sglebius 505290001Sglebius /* 506290001Sglebius * In the child, close all files except stdin, stdout, stderr, 507290001Sglebius * and the two child ends of the pipes. 508290001Sglebius */ 509290001Sglebius DEBUG_INSIST(-1 == c->req_read_pipe); 510290001Sglebius DEBUG_INSIST(-1 == c->resp_write_pipe); 511290001Sglebius c->req_read_pipe = blocking_pipes[0]; 512290001Sglebius c->resp_write_pipe = blocking_pipes[3]; 513290001Sglebius 514290001Sglebius kill_asyncio(0); 515290001Sglebius closelog(); 516290001Sglebius if (syslog_file != NULL) { 517290001Sglebius fclose(syslog_file); 518290001Sglebius syslog_file = NULL; 519290001Sglebius syslogit = TRUE; 520290001Sglebius } 521290001Sglebius keep_fd = max(c->req_read_pipe, c->resp_write_pipe); 522290001Sglebius for (fd = 3; fd < keep_fd; fd++) 523290001Sglebius if (fd != c->req_read_pipe && 524290001Sglebius fd != c->resp_write_pipe) 525290001Sglebius close(fd); 526290001Sglebius close_all_beyond(keep_fd); 527290001Sglebius /* 528290001Sglebius * We get signals from refclock serial I/O on NetBSD in the 529290001Sglebius * worker if we do not reset SIGIO's handler to the default. 530290001Sglebius * It is not conditionalized for NetBSD alone because on 531290001Sglebius * systems where it is not needed, it is harmless, and that 532290001Sglebius * allows us to handle unknown others with NetBSD behavior. 533290001Sglebius * [Bug 1386] 534290001Sglebius */ 535290001Sglebius#if defined(USE_SIGIO) 536290001Sglebius signal_no_reset(SIGIO, SIG_DFL); 537290001Sglebius#elif defined(USE_SIGPOLL) 538290001Sglebius signal_no_reset(SIGPOLL, SIG_DFL); 539290001Sglebius#endif 540290001Sglebius signal_no_reset(SIGHUP, worker_sighup); 541290001Sglebius init_logging("ntp_intres", 0, FALSE); 542290001Sglebius setup_logfile(NULL); 543290001Sglebius 544290001Sglebius /* 545290001Sglebius * And now back to the portable code 546290001Sglebius */ 547290001Sglebius exit_worker(blocking_child_common(c)); 548290001Sglebius} 549290001Sglebius 550290001Sglebius 551298770Sdelphijvoid worker_global_lock(int inOrOut) 552298770Sdelphij{ 553298770Sdelphij (void)inOrOut; 554298770Sdelphij} 555298770Sdelphij 556290001Sglebius#else /* !WORK_FORK follows */ 557290001Sglebiuschar work_fork_nonempty_compilation_unit; 558290001Sglebius#endif 559