ntp_intres.c revision 280849
1275970Scy/* 2275970Scy * ntp_intres.c - Implements a generic blocking worker child or thread, 3275970Scy * initially to provide a nonblocking solution for DNS 4275970Scy * name to address lookups available with getaddrinfo(). 5275970Scy * 6275970Scy * This is a new implementation as of 2009 sharing the filename and 7275970Scy * very little else with the prior implementation, which used a 8275970Scy * temporary file to receive a single set of requests from the parent, 9275970Scy * and a NTP mode 7 authenticated request to push back responses. 10275970Scy * 11275970Scy * A primary goal in rewriting this code was the need to support the 12275970Scy * pool configuration directive's requirement to retrieve multiple 13275970Scy * addresses resolving a single name, which has previously been 14275970Scy * satisfied with blocking resolver calls from the ntpd mainline code. 15275970Scy * 16275970Scy * A secondary goal is to provide a generic mechanism for other 17275970Scy * blocking operations to be delegated to a worker using a common 18275970Scy * model for both Unix and Windows ntpd. ntp_worker.c, work_fork.c, 19275970Scy * and work_thread.c implement the generic mechanism. This file 20275970Scy * implements the two current consumers, getaddrinfo_sometime() and the 21275970Scy * presently unused getnameinfo_sometime(). 22275970Scy * 23275970Scy * Both routines deliver results to a callback and manage memory 24275970Scy * allocation, meaning there is no freeaddrinfo_sometime(). 25275970Scy * 26275970Scy * The initial implementation for Unix uses a pair of unidirectional 27275970Scy * pipes, one each for requests and responses, connecting the forked 28275970Scy * blocking child worker with the ntpd mainline. The threaded code 29275970Scy * uses arrays of pointers to queue requests and responses. 30275970Scy * 31275970Scy * The parent drives the process, including scheduling sleeps between 32275970Scy * retries. 33275970Scy * 34275970Scy * Memory is managed differently for a child process, which mallocs 35275970Scy * request buffers to read from the pipe into, whereas the threaded 36275970Scy * code mallocs a copy of the request to hand off to the worker via 37275970Scy * the queueing array. The resulting request buffer is free()d by 38275970Scy * platform-independent code. A wrinkle is the request needs to be 39275970Scy * available to the requestor during response processing. 40275970Scy * 41275970Scy * Response memory allocation is also platform-dependent. With a 42275970Scy * separate process and pipes, the response is free()d after being 43275970Scy * written to the pipe. With threads, the same memory is handed 44275970Scy * over and the requestor frees it after processing is completed. 45275970Scy * 46275970Scy * The code should be generalized to support threads on Unix using 47275970Scy * much of the same code used for Windows initially. 48275970Scy * 49275970Scy */ 50275970Scy#ifdef HAVE_CONFIG_H 51275970Scy# include <config.h> 52275970Scy#endif 53275970Scy 54275970Scy#include "ntp_workimpl.h" 55275970Scy 56275970Scy#ifdef WORKER 57275970Scy 58275970Scy#include <stdio.h> 59275970Scy#include <ctype.h> 60275970Scy#include <signal.h> 61275970Scy 62275970Scy/**/ 63275970Scy#ifdef HAVE_SYS_TYPES_H 64275970Scy# include <sys/types.h> 65275970Scy#endif 66275970Scy#ifdef HAVE_NETINET_IN_H 67275970Scy#include <netinet/in.h> 68275970Scy#endif 69275970Scy#include <arpa/inet.h> 70275970Scy/**/ 71275970Scy#ifdef HAVE_SYS_PARAM_H 72275970Scy# include <sys/param.h> 73275970Scy#endif 74275970Scy 75275970Scy#if !defined(HAVE_RES_INIT) && defined(HAVE___RES_INIT) 76275970Scy# define HAVE_RES_INIT 77275970Scy#endif 78275970Scy 79275970Scy#if defined(HAVE_RESOLV_H) && defined(HAVE_RES_INIT) 80275970Scy# ifdef HAVE_ARPA_NAMESER_H 81275970Scy# include <arpa/nameser.h> /* DNS HEADER struct */ 82275970Scy# endif 83275970Scy# ifdef HAVE_NETDB_H 84275970Scy# include <netdb.h> 85275970Scy# endif 86275970Scy# include <resolv.h> 87275970Scy# ifdef HAVE_INT32_ONLY_WITH_DNS 88275970Scy# define HAVE_INT32 89275970Scy# endif 90275970Scy# ifdef HAVE_U_INT32_ONLY_WITH_DNS 91275970Scy# define HAVE_U_INT32 92275970Scy# endif 93275970Scy#endif 94275970Scy 95275970Scy#include "ntp.h" 96275970Scy#include "ntp_debug.h" 97275970Scy#include "ntp_malloc.h" 98275970Scy#include "ntp_syslog.h" 99275970Scy#include "ntp_unixtime.h" 100275970Scy#include "ntp_intres.h" 101275970Scy#include "intreswork.h" 102275970Scy 103275970Scy 104275970Scy/* 105275970Scy * Following are implementations of getaddrinfo_sometime() and 106275970Scy * getnameinfo_sometime(). Each is implemented in three routines: 107275970Scy * 108275970Scy * getaddrinfo_sometime() getnameinfo_sometime() 109275970Scy * blocking_getaddrinfo() blocking_getnameinfo() 110275970Scy * getaddrinfo_sometime_complete() getnameinfo_sometime_complete() 111275970Scy * 112275970Scy * The first runs in the parent and marshalls (or serializes) request 113275970Scy * parameters into a request blob which is processed in the child by 114275970Scy * the second routine, blocking_*(), which serializes the results into 115275970Scy * a response blob unpacked by the third routine, *_complete(), which 116275970Scy * calls the callback routine provided with the request and frees 117275970Scy * _request_ memory allocated by the first routine. Response memory 118275970Scy * is managed by the code which calls the *_complete routines. 119275970Scy */ 120275970Scy 121275970Scy/* === typedefs === */ 122275970Scytypedef struct blocking_gai_req_tag { /* marshalled args */ 123275970Scy size_t octets; 124275970Scy u_int dns_idx; 125275970Scy time_t scheduled; 126275970Scy time_t earliest; 127275970Scy struct addrinfo hints; 128275970Scy int retry; 129275970Scy gai_sometime_callback callback; 130275970Scy void * context; 131275970Scy size_t nodesize; 132275970Scy size_t servsize; 133275970Scy} blocking_gai_req; 134275970Scy 135275970Scytypedef struct blocking_gai_resp_tag { 136275970Scy size_t octets; 137275970Scy int retcode; 138275970Scy int retry; 139275970Scy int gai_errno; /* for EAI_SYSTEM case */ 140275970Scy int ai_count; 141275970Scy /* 142275970Scy * Followed by ai_count struct addrinfo and then ai_count 143275970Scy * sockaddr_u and finally the canonical name strings. 144275970Scy */ 145275970Scy} blocking_gai_resp; 146275970Scy 147275970Scytypedef struct blocking_gni_req_tag { 148275970Scy size_t octets; 149275970Scy u_int dns_idx; 150275970Scy time_t scheduled; 151275970Scy time_t earliest; 152275970Scy int retry; 153275970Scy size_t hostoctets; 154275970Scy size_t servoctets; 155275970Scy int flags; 156275970Scy gni_sometime_callback callback; 157275970Scy void * context; 158275970Scy sockaddr_u socku; 159275970Scy} blocking_gni_req; 160275970Scy 161275970Scytypedef struct blocking_gni_resp_tag { 162275970Scy size_t octets; 163275970Scy int retcode; 164275970Scy int gni_errno; /* for EAI_SYSTEM case */ 165275970Scy int retry; 166275970Scy size_t hostoctets; 167275970Scy size_t servoctets; 168275970Scy /* 169275970Scy * Followed by hostoctets bytes of null-terminated host, 170275970Scy * then servoctets bytes of null-terminated service. 171275970Scy */ 172275970Scy} blocking_gni_resp; 173275970Scy 174275970Scy/* per-DNS-worker state in parent */ 175275970Scytypedef struct dnschild_ctx_tag { 176275970Scy u_int index; 177275970Scy time_t next_dns_timeslot; 178275970Scy} dnschild_ctx; 179275970Scy 180275970Scy/* per-DNS-worker state in worker */ 181275970Scytypedef struct dnsworker_ctx_tag { 182275970Scy blocking_child * c; 183275970Scy time_t ignore_scheduled_before; 184275970Scy#ifdef HAVE_RES_INIT 185275970Scy time_t next_res_init; 186275970Scy#endif 187275970Scy} dnsworker_ctx; 188275970Scy 189275970Scy 190275970Scy/* === variables === */ 191275970Scydnschild_ctx ** dnschild_contexts; /* parent */ 192275970Scyu_int dnschild_contexts_alloc; 193275970Scydnsworker_ctx ** dnsworker_contexts; /* child */ 194275970Scyu_int dnsworker_contexts_alloc; 195275970Scy 196275970Scy#ifdef HAVE_RES_INIT 197275970Scystatic time_t next_res_init; 198275970Scy#endif 199275970Scy 200275970Scy 201275970Scy/* === forward declarations === */ 202275970Scystatic u_int reserve_dnschild_ctx(void); 203275970Scystatic u_int get_dnschild_ctx(void); 204275970Scystatic void alloc_dnsworker_context(u_int); 205275970Scy/* static void free_dnsworker_context(u_int); */ 206275970Scystatic dnsworker_ctx * get_worker_context(blocking_child *, u_int); 207275970Scystatic void scheduled_sleep(time_t, time_t, 208275970Scy dnsworker_ctx *); 209275970Scystatic void manage_dns_retry_interval(time_t *, time_t *, 210275970Scy int *, 211275970Scy time_t *); 212275970Scystatic int should_retry_dns(int, int); 213275970Scy#ifdef HAVE_RES_INIT 214275970Scystatic void reload_resolv_conf(dnsworker_ctx *); 215275970Scy#else 216275970Scy# define reload_resolv_conf(wc) \ 217275970Scy do { \ 218275970Scy (void)(wc); \ 219275970Scy } while (FALSE) 220275970Scy#endif 221275970Scystatic void getaddrinfo_sometime_complete(blocking_work_req, 222275970Scy void *, size_t, 223275970Scy void *); 224275970Scystatic void getnameinfo_sometime_complete(blocking_work_req, 225275970Scy void *, size_t, 226275970Scy void *); 227275970Scy 228275970Scy 229275970Scy/* === functions === */ 230275970Scy/* 231275970Scy * getaddrinfo_sometime - uses blocking child to call getaddrinfo then 232275970Scy * invokes provided callback completion function. 233275970Scy */ 234275970Scyint 235275970Scygetaddrinfo_sometime( 236275970Scy const char * node, 237275970Scy const char * service, 238275970Scy const struct addrinfo * hints, 239275970Scy int retry, 240275970Scy gai_sometime_callback callback, 241275970Scy void * context 242275970Scy ) 243275970Scy{ 244275970Scy blocking_gai_req * gai_req; 245275970Scy u_int idx; 246275970Scy dnschild_ctx * child_ctx; 247275970Scy size_t req_size; 248275970Scy size_t nodesize; 249275970Scy size_t servsize; 250275970Scy time_t now; 251275970Scy 252275970Scy NTP_REQUIRE(NULL != node); 253275970Scy if (NULL != hints) { 254275970Scy NTP_REQUIRE(0 == hints->ai_addrlen); 255275970Scy NTP_REQUIRE(NULL == hints->ai_addr); 256275970Scy NTP_REQUIRE(NULL == hints->ai_canonname); 257275970Scy NTP_REQUIRE(NULL == hints->ai_next); 258275970Scy } 259275970Scy 260275970Scy idx = get_dnschild_ctx(); 261275970Scy child_ctx = dnschild_contexts[idx]; 262275970Scy 263275970Scy nodesize = strlen(node) + 1; 264275970Scy servsize = strlen(service) + 1; 265275970Scy req_size = sizeof(*gai_req) + nodesize + servsize; 266275970Scy 267275970Scy gai_req = emalloc_zero(req_size); 268275970Scy 269275970Scy gai_req->octets = req_size; 270275970Scy gai_req->dns_idx = idx; 271275970Scy now = time(NULL); 272275970Scy gai_req->scheduled = now; 273275970Scy gai_req->earliest = max(now, child_ctx->next_dns_timeslot); 274275970Scy child_ctx->next_dns_timeslot = gai_req->earliest; 275275970Scy if (hints != NULL) 276275970Scy gai_req->hints = *hints; 277275970Scy gai_req->retry = retry; 278275970Scy gai_req->callback = callback; 279275970Scy gai_req->context = context; 280275970Scy gai_req->nodesize = nodesize; 281275970Scy gai_req->servsize = servsize; 282275970Scy 283275970Scy memcpy((char *)gai_req + sizeof(*gai_req), node, nodesize); 284275970Scy memcpy((char *)gai_req + sizeof(*gai_req) + nodesize, service, 285275970Scy servsize); 286275970Scy 287275970Scy if (queue_blocking_request( 288275970Scy BLOCKING_GETADDRINFO, 289275970Scy gai_req, 290275970Scy req_size, 291275970Scy &getaddrinfo_sometime_complete, 292275970Scy gai_req)) { 293275970Scy 294275970Scy msyslog(LOG_ERR, "unable to queue getaddrinfo request"); 295275970Scy errno = EFAULT; 296275970Scy return -1; 297275970Scy } 298275970Scy 299275970Scy return 0; 300275970Scy} 301275970Scy 302275970Scyint 303275970Scyblocking_getaddrinfo( 304275970Scy blocking_child * c, 305275970Scy blocking_pipe_header * req 306275970Scy ) 307275970Scy{ 308275970Scy blocking_gai_req * gai_req; 309275970Scy dnsworker_ctx * worker_ctx; 310275970Scy blocking_pipe_header * resp; 311275970Scy blocking_gai_resp * gai_resp; 312275970Scy char * node; 313275970Scy char * service; 314275970Scy struct addrinfo * ai_res; 315275970Scy struct addrinfo * ai; 316275970Scy struct addrinfo * serialized_ai; 317275970Scy size_t canons_octets; 318275970Scy size_t this_octets; 319275970Scy size_t resp_octets; 320275970Scy char * cp; 321275970Scy time_t time_now; 322275970Scy 323275970Scy gai_req = (void *)((char *)req + sizeof(*req)); 324275970Scy node = (char *)gai_req + sizeof(*gai_req); 325275970Scy service = node + gai_req->nodesize; 326275970Scy 327275970Scy worker_ctx = get_worker_context(c, gai_req->dns_idx); 328275970Scy scheduled_sleep(gai_req->scheduled, gai_req->earliest, 329275970Scy worker_ctx); 330275970Scy reload_resolv_conf(worker_ctx); 331275970Scy 332275970Scy /* 333275970Scy * Take a shot at the final size, better to overestimate 334275970Scy * at first and then realloc to a smaller size. 335275970Scy */ 336275970Scy 337275970Scy resp_octets = sizeof(*resp) + sizeof(*gai_resp) + 338275970Scy 16 * (sizeof(struct addrinfo) + 339275970Scy sizeof(sockaddr_u)) + 340275970Scy 256; 341275970Scy resp = emalloc_zero(resp_octets); 342275970Scy gai_resp = (void *)(resp + 1); 343275970Scy 344275970Scy TRACE(2, ("blocking_getaddrinfo given node %s serv %s fam %d flags %x\n", 345275970Scy node, service, gai_req->hints.ai_family, 346275970Scy gai_req->hints.ai_flags)); 347275970Scy#ifdef DEBUG 348275970Scy if (debug >= 2) 349275970Scy fflush(stdout); 350275970Scy#endif 351275970Scy ai_res = NULL; 352275970Scy gai_resp->retcode = getaddrinfo(node, service, &gai_req->hints, 353275970Scy &ai_res); 354275970Scy gai_resp->retry = gai_req->retry; 355275970Scy#ifdef EAI_SYSTEM 356275970Scy if (EAI_SYSTEM == gai_resp->retcode) 357275970Scy gai_resp->gai_errno = errno; 358275970Scy#endif 359275970Scy canons_octets = 0; 360275970Scy 361275970Scy if (0 == gai_resp->retcode) { 362275970Scy ai = ai_res; 363275970Scy while (NULL != ai) { 364275970Scy gai_resp->ai_count++; 365275970Scy if (ai->ai_canonname) 366275970Scy canons_octets += strlen(ai->ai_canonname) + 1; 367275970Scy ai = ai->ai_next; 368275970Scy } 369275970Scy /* 370275970Scy * If this query succeeded only after retrying, DNS may have 371275970Scy * just become responsive. Ignore previously-scheduled 372275970Scy * retry sleeps once for each pending request, similar to 373275970Scy * the way scheduled_sleep() does when its worker_sleep() 374275970Scy * is interrupted. 375275970Scy */ 376275970Scy if (gai_resp->retry > INITIAL_DNS_RETRY) { 377275970Scy time_now = time(NULL); 378275970Scy worker_ctx->ignore_scheduled_before = time_now; 379275970Scy TRACE(1, ("DNS success after retry, ignoring sleeps scheduled before now (%s)\n", 380275970Scy humantime(time_now))); 381275970Scy } 382275970Scy } 383275970Scy 384275970Scy /* 385275970Scy * Our response consists of a header, followed by ai_count 386275970Scy * addrinfo structs followed by ai_count sockaddr_storage 387275970Scy * structs followed by the canonical names. 388275970Scy */ 389275970Scy gai_resp->octets = sizeof(*gai_resp) 390275970Scy + gai_resp->ai_count 391275970Scy * (sizeof(gai_req->hints) 392275970Scy + sizeof(sockaddr_u)) 393275970Scy + canons_octets; 394275970Scy 395275970Scy resp_octets = sizeof(*resp) + gai_resp->octets; 396275970Scy resp = erealloc(resp, resp_octets); 397275970Scy gai_resp = (void *)(resp + 1); 398275970Scy 399275970Scy /* cp serves as our current pointer while serializing */ 400275970Scy cp = (void *)(gai_resp + 1); 401275970Scy canons_octets = 0; 402275970Scy 403275970Scy if (0 == gai_resp->retcode) { 404275970Scy ai = ai_res; 405275970Scy while (NULL != ai) { 406275970Scy memcpy(cp, ai, sizeof(*ai)); 407275970Scy serialized_ai = (void *)cp; 408275970Scy cp += sizeof(*ai); 409275970Scy 410275970Scy /* transform ai_canonname into offset */ 411275970Scy if (NULL != serialized_ai->ai_canonname) { 412275970Scy serialized_ai->ai_canonname = (char *)canons_octets; 413275970Scy canons_octets += strlen(ai->ai_canonname) + 1; 414275970Scy } 415275970Scy 416275970Scy /* leave fixup of ai_addr pointer for receiver */ 417275970Scy 418275970Scy ai = ai->ai_next; 419275970Scy } 420275970Scy 421275970Scy ai = ai_res; 422275970Scy while (NULL != ai) { 423275970Scy NTP_INSIST(ai->ai_addrlen <= sizeof(sockaddr_u)); 424275970Scy memcpy(cp, ai->ai_addr, ai->ai_addrlen); 425275970Scy cp += sizeof(sockaddr_u); 426275970Scy 427275970Scy ai = ai->ai_next; 428275970Scy } 429275970Scy 430275970Scy ai = ai_res; 431275970Scy while (NULL != ai) { 432275970Scy if (NULL != ai->ai_canonname) { 433275970Scy this_octets = strlen(ai->ai_canonname) + 1; 434275970Scy memcpy(cp, ai->ai_canonname, this_octets); 435275970Scy cp += this_octets; 436275970Scy } 437275970Scy 438275970Scy ai = ai->ai_next; 439275970Scy } 440275970Scy freeaddrinfo(ai_res); 441275970Scy } 442275970Scy 443275970Scy /* 444275970Scy * make sure our walk and earlier calc match 445275970Scy */ 446275970Scy DEBUG_INSIST((size_t)(cp - (char *)resp) == resp_octets); 447275970Scy 448275970Scy if (queue_blocking_response(c, resp, resp_octets, req)) { 449275970Scy msyslog(LOG_ERR, "blocking_getaddrinfo can not queue response"); 450275970Scy return -1; 451275970Scy } 452275970Scy 453275970Scy return 0; 454275970Scy} 455275970Scy 456275970Scy 457275970Scystatic void 458275970Scygetaddrinfo_sometime_complete( 459275970Scy blocking_work_req rtype, 460275970Scy void * context, 461275970Scy size_t respsize, 462275970Scy void * resp 463275970Scy ) 464275970Scy{ 465275970Scy blocking_gai_req * gai_req; 466275970Scy blocking_gai_resp * gai_resp; 467275970Scy dnschild_ctx * child_ctx; 468275970Scy struct addrinfo * ai; 469275970Scy struct addrinfo * next_ai; 470275970Scy sockaddr_u * psau; 471275970Scy char * node; 472275970Scy char * service; 473275970Scy char * canon_start; 474275970Scy time_t time_now; 475275970Scy int again; 476275970Scy int af; 477275970Scy const char * fam_spec; 478275970Scy int i; 479275970Scy 480275970Scy gai_req = context; 481275970Scy gai_resp = resp; 482275970Scy 483275970Scy DEBUG_REQUIRE(BLOCKING_GETADDRINFO == rtype); 484275970Scy DEBUG_REQUIRE(respsize == gai_resp->octets); 485275970Scy 486275970Scy node = (char *)gai_req + sizeof(*gai_req); 487275970Scy service = node + gai_req->nodesize; 488275970Scy 489275970Scy child_ctx = dnschild_contexts[gai_req->dns_idx]; 490275970Scy 491275970Scy if (0 == gai_resp->retcode) { 492275970Scy /* 493275970Scy * If this query succeeded only after retrying, DNS may have 494275970Scy * just become responsive. 495275970Scy */ 496275970Scy if (gai_resp->retry > INITIAL_DNS_RETRY) { 497275970Scy time_now = time(NULL); 498275970Scy child_ctx->next_dns_timeslot = time_now; 499275970Scy TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n", 500275970Scy gai_req->dns_idx, humantime(time_now))); 501275970Scy } 502275970Scy } else { 503275970Scy again = should_retry_dns(gai_resp->retcode, 504275970Scy gai_resp->gai_errno); 505275970Scy /* 506275970Scy * exponential backoff of DNS retries to 64s 507275970Scy */ 508275970Scy if (gai_req->retry > 0 && again) { 509275970Scy /* log the first retry only */ 510275970Scy if (INITIAL_DNS_RETRY == gai_req->retry) 511275970Scy NLOG(NLOG_SYSINFO) { 512275970Scy af = gai_req->hints.ai_family; 513275970Scy fam_spec = (AF_INET6 == af) 514275970Scy ? " (AAAA)" 515275970Scy : (AF_INET == af) 516275970Scy ? " (A)" 517275970Scy : ""; 518275970Scy#ifdef EAI_SYSTEM 519275970Scy if (EAI_SYSTEM == gai_resp->retcode) { 520275970Scy errno = gai_resp->gai_errno; 521275970Scy msyslog(LOG_INFO, 522275970Scy "retrying DNS %s%s: EAI_SYSTEM %d: %m", 523275970Scy node, fam_spec, 524275970Scy gai_resp->gai_errno); 525275970Scy } else 526275970Scy#endif 527275970Scy msyslog(LOG_INFO, 528275970Scy "retrying DNS %s%s: %s (%d)", 529275970Scy node, fam_spec, 530275970Scy gai_strerror(gai_resp->retcode), 531275970Scy gai_resp->retcode); 532275970Scy } 533275970Scy manage_dns_retry_interval(&gai_req->scheduled, 534275970Scy &gai_req->earliest, &gai_req->retry, 535275970Scy &child_ctx->next_dns_timeslot); 536275970Scy if (!queue_blocking_request( 537275970Scy BLOCKING_GETADDRINFO, 538275970Scy gai_req, 539275970Scy gai_req->octets, 540275970Scy &getaddrinfo_sometime_complete, 541275970Scy gai_req)) 542275970Scy return; 543275970Scy else 544275970Scy msyslog(LOG_ERR, 545275970Scy "unable to retry hostname %s", 546275970Scy node); 547275970Scy } 548275970Scy } 549275970Scy 550275970Scy /* 551275970Scy * fixup pointers in returned addrinfo array 552275970Scy */ 553275970Scy ai = (void *)((char *)gai_resp + sizeof(*gai_resp)); 554275970Scy next_ai = NULL; 555275970Scy for (i = gai_resp->ai_count - 1; i >= 0; i--) { 556275970Scy ai[i].ai_next = next_ai; 557275970Scy next_ai = &ai[i]; 558275970Scy } 559275970Scy 560275970Scy psau = (void *)((char *)ai + gai_resp->ai_count * sizeof(*ai)); 561275970Scy canon_start = (char *)psau + gai_resp->ai_count * sizeof(*psau); 562275970Scy 563275970Scy for (i = 0; i < gai_resp->ai_count; i++) { 564275970Scy if (NULL != ai[i].ai_addr) 565275970Scy ai[i].ai_addr = &psau->sa; 566275970Scy psau++; 567275970Scy if (NULL != ai[i].ai_canonname) 568275970Scy ai[i].ai_canonname += (size_t)canon_start; 569275970Scy } 570275970Scy 571275970Scy NTP_ENSURE((char *)psau == canon_start); 572275970Scy 573275970Scy if (!gai_resp->ai_count) 574275970Scy ai = NULL; 575275970Scy 576275970Scy (*gai_req->callback)(gai_resp->retcode, gai_resp->gai_errno, 577275970Scy gai_req->context, node, service, 578275970Scy &gai_req->hints, ai); 579275970Scy 580275970Scy free(gai_req); 581275970Scy /* gai_resp is part of block freed by process_blocking_resp() */ 582275970Scy} 583275970Scy 584275970Scy 585275970Scy#ifdef TEST_BLOCKING_WORKER 586275970Scyvoid gai_test_callback(int rescode, int gai_errno, void *context, const char *name, const char *service, const struct addrinfo *hints, const struct addrinfo *ai_res) 587275970Scy{ 588275970Scy sockaddr_u addr; 589275970Scy 590275970Scy if (rescode) { 591275970Scy TRACE(1, ("gai_test_callback context %p error rescode %d %s serv %s\n", 592275970Scy context, rescode, name, service)); 593275970Scy return; 594275970Scy } 595275970Scy while (!rescode && NULL != ai_res) { 596275970Scy ZERO_SOCK(&addr); 597275970Scy memcpy(&addr, ai_res->ai_addr, ai_res->ai_addrlen); 598275970Scy TRACE(1, ("ctx %p fam %d addr %s canon '%s' type %s at %p ai_addr %p ai_next %p\n", 599275970Scy context, 600275970Scy AF(&addr), 601275970Scy stoa(&addr), 602275970Scy (ai_res->ai_canonname) 603275970Scy ? ai_res->ai_canonname 604275970Scy : "", 605275970Scy (SOCK_DGRAM == ai_res->ai_socktype) 606275970Scy ? "DGRAM" 607275970Scy : (SOCK_STREAM == ai_res->ai_socktype) 608275970Scy ? "STREAM" 609275970Scy : "(other)", 610275970Scy ai_res, 611275970Scy ai_res->ai_addr, 612275970Scy ai_res->ai_next)); 613275970Scy 614275970Scy getnameinfo_sometime((sockaddr_u *)ai_res->ai_addr, 128, 32, 0, gni_test_callback, context); 615275970Scy 616275970Scy ai_res = ai_res->ai_next; 617275970Scy } 618275970Scy} 619275970Scy#endif /* TEST_BLOCKING_WORKER */ 620275970Scy 621275970Scy 622275970Scyint 623275970Scygetnameinfo_sometime( 624275970Scy sockaddr_u * psau, 625275970Scy size_t hostoctets, 626275970Scy size_t servoctets, 627275970Scy int flags, 628275970Scy gni_sometime_callback callback, 629275970Scy void * context 630275970Scy ) 631275970Scy{ 632275970Scy blocking_gni_req * gni_req; 633275970Scy u_int idx; 634275970Scy dnschild_ctx * child_ctx; 635275970Scy time_t time_now; 636275970Scy 637275970Scy NTP_REQUIRE(hostoctets); 638275970Scy NTP_REQUIRE(hostoctets + servoctets < 1024); 639275970Scy 640275970Scy idx = get_dnschild_ctx(); 641275970Scy child_ctx = dnschild_contexts[idx]; 642275970Scy 643275970Scy gni_req = emalloc_zero(sizeof(*gni_req)); 644275970Scy 645275970Scy gni_req->octets = sizeof(*gni_req); 646275970Scy gni_req->dns_idx = idx; 647275970Scy time_now = time(NULL); 648275970Scy gni_req->scheduled = time_now; 649275970Scy gni_req->earliest = max(time_now, child_ctx->next_dns_timeslot); 650275970Scy child_ctx->next_dns_timeslot = gni_req->earliest; 651275970Scy memcpy(&gni_req->socku, psau, SOCKLEN(psau)); 652275970Scy gni_req->hostoctets = hostoctets; 653275970Scy gni_req->servoctets = servoctets; 654275970Scy gni_req->flags = flags; 655275970Scy gni_req->retry = INITIAL_DNS_RETRY; 656275970Scy gni_req->callback = callback; 657275970Scy gni_req->context = context; 658275970Scy 659275970Scy if (queue_blocking_request( 660275970Scy BLOCKING_GETNAMEINFO, 661275970Scy gni_req, 662275970Scy sizeof(*gni_req), 663275970Scy &getnameinfo_sometime_complete, 664275970Scy gni_req)) { 665275970Scy 666275970Scy msyslog(LOG_ERR, "unable to queue getnameinfo request"); 667275970Scy errno = EFAULT; 668275970Scy return -1; 669275970Scy } 670275970Scy 671275970Scy return 0; 672275970Scy} 673275970Scy 674275970Scy 675275970Scyint 676275970Scyblocking_getnameinfo( 677275970Scy blocking_child * c, 678275970Scy blocking_pipe_header * req 679275970Scy ) 680275970Scy{ 681275970Scy blocking_gni_req * gni_req; 682275970Scy dnsworker_ctx * worker_ctx; 683275970Scy blocking_pipe_header * resp; 684275970Scy blocking_gni_resp * gni_resp; 685275970Scy size_t octets; 686275970Scy size_t resp_octets; 687275970Scy char * service; 688275970Scy char * cp; 689275970Scy int rc; 690275970Scy time_t time_now; 691280849Scy char host[1024]; 692275970Scy 693275970Scy gni_req = (void *)((char *)req + sizeof(*req)); 694275970Scy 695275970Scy octets = gni_req->hostoctets + gni_req->servoctets; 696275970Scy 697275970Scy /* 698275970Scy * Some alloca() implementations are fragile regarding 699275970Scy * large allocations. We only need room for the host 700275970Scy * and service names. 701275970Scy */ 702280849Scy NTP_REQUIRE(octets < sizeof(host)); 703275970Scy service = host + gni_req->hostoctets; 704275970Scy 705275970Scy worker_ctx = get_worker_context(c, gni_req->dns_idx); 706275970Scy scheduled_sleep(gni_req->scheduled, gni_req->earliest, 707275970Scy worker_ctx); 708275970Scy reload_resolv_conf(worker_ctx); 709275970Scy 710275970Scy /* 711275970Scy * Take a shot at the final size, better to overestimate 712275970Scy * then realloc to a smaller size. 713275970Scy */ 714275970Scy 715275970Scy resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets; 716275970Scy resp = emalloc_zero(resp_octets); 717275970Scy gni_resp = (void *)((char *)resp + sizeof(*resp)); 718275970Scy 719275970Scy TRACE(2, ("blocking_getnameinfo given addr %s flags 0x%x hostlen %lu servlen %lu\n", 720275970Scy stoa(&gni_req->socku), gni_req->flags, 721275970Scy (u_long)gni_req->hostoctets, (u_long)gni_req->servoctets)); 722275970Scy 723275970Scy gni_resp->retcode = getnameinfo(&gni_req->socku.sa, 724275970Scy SOCKLEN(&gni_req->socku), 725275970Scy host, 726275970Scy gni_req->hostoctets, 727275970Scy service, 728275970Scy gni_req->servoctets, 729275970Scy gni_req->flags); 730275970Scy gni_resp->retry = gni_req->retry; 731275970Scy#ifdef EAI_SYSTEM 732275970Scy if (EAI_SYSTEM == gni_resp->retcode) 733275970Scy gni_resp->gni_errno = errno; 734275970Scy#endif 735275970Scy 736275970Scy if (0 != gni_resp->retcode) { 737275970Scy gni_resp->hostoctets = 0; 738275970Scy gni_resp->servoctets = 0; 739275970Scy } else { 740275970Scy gni_resp->hostoctets = strlen(host) + 1; 741275970Scy gni_resp->servoctets = strlen(service) + 1; 742275970Scy /* 743275970Scy * If this query succeeded only after retrying, DNS may have 744275970Scy * just become responsive. Ignore previously-scheduled 745275970Scy * retry sleeps once for each pending request, similar to 746275970Scy * the way scheduled_sleep() does when its worker_sleep() 747275970Scy * is interrupted. 748275970Scy */ 749275970Scy if (gni_req->retry > INITIAL_DNS_RETRY) { 750275970Scy time_now = time(NULL); 751275970Scy worker_ctx->ignore_scheduled_before = time_now; 752275970Scy TRACE(1, ("DNS success after retrying, ignoring sleeps scheduled before now (%s)\n", 753275970Scy humantime(time_now))); 754275970Scy } 755275970Scy } 756275970Scy octets = gni_resp->hostoctets + gni_resp->servoctets; 757275970Scy /* 758275970Scy * Our response consists of a header, followed by the host and 759275970Scy * service strings, each null-terminated. 760275970Scy */ 761275970Scy resp_octets = sizeof(*resp) + sizeof(*gni_resp) + octets; 762275970Scy 763275970Scy resp = erealloc(resp, resp_octets); 764275970Scy gni_resp = (void *)(resp + 1); 765275970Scy 766275970Scy gni_resp->octets = sizeof(*gni_resp) + octets; 767275970Scy 768275970Scy /* cp serves as our current pointer while serializing */ 769275970Scy cp = (void *)(gni_resp + 1); 770275970Scy 771275970Scy if (0 == gni_resp->retcode) { 772275970Scy memcpy(cp, host, gni_resp->hostoctets); 773275970Scy cp += gni_resp->hostoctets; 774275970Scy memcpy(cp, service, gni_resp->servoctets); 775275970Scy cp += gni_resp->servoctets; 776275970Scy } 777275970Scy 778275970Scy NTP_INSIST((size_t)(cp - (char *)resp) == resp_octets); 779275970Scy NTP_INSIST(resp_octets - sizeof(*resp) == gni_resp->octets); 780275970Scy 781275970Scy rc = queue_blocking_response(c, resp, resp_octets, req); 782275970Scy if (rc) 783275970Scy msyslog(LOG_ERR, "blocking_getnameinfo unable to queue response"); 784275970Scy return rc; 785275970Scy} 786275970Scy 787275970Scy 788275970Scystatic void 789275970Scygetnameinfo_sometime_complete( 790275970Scy blocking_work_req rtype, 791275970Scy void * context, 792275970Scy size_t respsize, 793275970Scy void * resp 794275970Scy ) 795275970Scy{ 796275970Scy blocking_gni_req * gni_req; 797275970Scy blocking_gni_resp * gni_resp; 798275970Scy dnschild_ctx * child_ctx; 799275970Scy char * host; 800275970Scy char * service; 801275970Scy time_t time_now; 802275970Scy int again; 803275970Scy 804275970Scy gni_req = context; 805275970Scy gni_resp = resp; 806275970Scy 807275970Scy DEBUG_REQUIRE(BLOCKING_GETNAMEINFO == rtype); 808275970Scy DEBUG_REQUIRE(respsize == gni_resp->octets); 809275970Scy 810275970Scy child_ctx = dnschild_contexts[gni_req->dns_idx]; 811275970Scy 812275970Scy if (0 == gni_resp->retcode) { 813275970Scy /* 814275970Scy * If this query succeeded only after retrying, DNS may have 815275970Scy * just become responsive. 816275970Scy */ 817275970Scy if (gni_resp->retry > INITIAL_DNS_RETRY) { 818275970Scy time_now = time(NULL); 819275970Scy child_ctx->next_dns_timeslot = time_now; 820275970Scy TRACE(1, ("DNS success after retry, %u next_dns_timeslot reset (%s)\n", 821275970Scy gni_req->dns_idx, humantime(time_now))); 822275970Scy } 823275970Scy } else { 824275970Scy again = should_retry_dns(gni_resp->retcode, gni_resp->gni_errno); 825275970Scy /* 826275970Scy * exponential backoff of DNS retries to 64s 827275970Scy */ 828275970Scy if (gni_req->retry > 0) 829275970Scy manage_dns_retry_interval(&gni_req->scheduled, 830275970Scy &gni_req->earliest, &gni_req->retry, 831275970Scy &child_ctx->next_dns_timeslot); 832275970Scy 833275970Scy if (gni_req->retry > 0 && again) { 834275970Scy if (!queue_blocking_request( 835275970Scy BLOCKING_GETNAMEINFO, 836275970Scy gni_req, 837275970Scy gni_req->octets, 838275970Scy &getnameinfo_sometime_complete, 839275970Scy gni_req)) 840275970Scy return; 841275970Scy 842275970Scy msyslog(LOG_ERR, "unable to retry reverse lookup of %s", stoa(&gni_req->socku)); 843275970Scy } 844275970Scy } 845275970Scy 846275970Scy if (!gni_resp->hostoctets) { 847275970Scy host = NULL; 848275970Scy service = NULL; 849275970Scy } else { 850275970Scy host = (char *)gni_resp + sizeof(*gni_resp); 851275970Scy service = (gni_resp->servoctets) 852275970Scy ? host + gni_resp->hostoctets 853275970Scy : NULL; 854275970Scy } 855275970Scy 856275970Scy (*gni_req->callback)(gni_resp->retcode, gni_resp->gni_errno, 857275970Scy &gni_req->socku, gni_req->flags, host, 858275970Scy service, gni_req->context); 859275970Scy 860275970Scy free(gni_req); 861275970Scy /* gni_resp is part of block freed by process_blocking_resp() */ 862275970Scy} 863275970Scy 864275970Scy 865275970Scy#ifdef TEST_BLOCKING_WORKER 866275970Scyvoid gni_test_callback(int rescode, int gni_errno, sockaddr_u *psau, int flags, const char *host, const char *service, void *context) 867275970Scy{ 868275970Scy if (!rescode) 869275970Scy TRACE(1, ("gni_test_callback got host '%s' serv '%s' for addr %s context %p\n", 870275970Scy host, service, stoa(psau), context)); 871275970Scy else 872275970Scy TRACE(1, ("gni_test_callback context %p rescode %d gni_errno %d flags 0x%x addr %s\n", 873275970Scy context, rescode, gni_errno, flags, stoa(psau))); 874275970Scy} 875275970Scy#endif /* TEST_BLOCKING_WORKER */ 876275970Scy 877275970Scy 878275970Scy#ifdef HAVE_RES_INIT 879275970Scystatic void 880275970Scyreload_resolv_conf( 881275970Scy dnsworker_ctx * worker_ctx 882275970Scy ) 883275970Scy{ 884275970Scy time_t time_now; 885275970Scy 886275970Scy /* 887275970Scy * This is ad-hoc. Reload /etc/resolv.conf once per minute 888275970Scy * to pick up on changes from the DHCP client. [Bug 1226] 889275970Scy * When using threads for the workers, this needs to happen 890275970Scy * only once per minute process-wide. 891275970Scy */ 892275970Scy time_now = time(NULL); 893275970Scy# ifdef WORK_THREAD 894275970Scy worker_ctx->next_res_init = next_res_init; 895275970Scy# endif 896275970Scy if (worker_ctx->next_res_init <= time_now) { 897275970Scy if (worker_ctx->next_res_init != 0) 898275970Scy res_init(); 899275970Scy worker_ctx->next_res_init = time_now + 60; 900275970Scy# ifdef WORK_THREAD 901275970Scy next_res_init = worker_ctx->next_res_init; 902275970Scy# endif 903275970Scy } 904275970Scy} 905275970Scy#endif /* HAVE_RES_INIT */ 906275970Scy 907275970Scy 908275970Scystatic u_int 909275970Scyreserve_dnschild_ctx(void) 910275970Scy{ 911275970Scy const size_t ps = sizeof(dnschild_contexts[0]); 912275970Scy const size_t cs = sizeof(*dnschild_contexts[0]); 913275970Scy u_int c; 914275970Scy u_int new_alloc; 915275970Scy size_t octets; 916275970Scy size_t new_octets; 917275970Scy 918275970Scy c = 0; 919275970Scy while (TRUE) { 920275970Scy for ( ; c < dnschild_contexts_alloc; c++) { 921275970Scy if (NULL == dnschild_contexts[c]) { 922275970Scy dnschild_contexts[c] = emalloc_zero(cs); 923275970Scy 924275970Scy return c; 925275970Scy } 926275970Scy } 927275970Scy new_alloc = dnschild_contexts_alloc + 20; 928275970Scy new_octets = new_alloc * ps; 929275970Scy octets = dnschild_contexts_alloc * ps; 930275970Scy dnschild_contexts = erealloc_zero(dnschild_contexts, 931275970Scy new_octets, octets); 932275970Scy dnschild_contexts_alloc = new_alloc; 933275970Scy } 934275970Scy} 935275970Scy 936275970Scy 937275970Scystatic u_int 938275970Scyget_dnschild_ctx(void) 939275970Scy{ 940275970Scy static u_int shared_ctx = UINT_MAX; 941275970Scy 942275970Scy if (worker_per_query) 943275970Scy return reserve_dnschild_ctx(); 944275970Scy 945275970Scy if (UINT_MAX == shared_ctx) 946275970Scy shared_ctx = reserve_dnschild_ctx(); 947275970Scy 948275970Scy return shared_ctx; 949275970Scy} 950275970Scy 951275970Scy 952275970Scystatic void 953275970Scyalloc_dnsworker_context( 954275970Scy u_int idx 955275970Scy ) 956275970Scy{ 957275970Scy const size_t worker_context_sz = sizeof(*dnsworker_contexts[0]); 958275970Scy 959275970Scy REQUIRE(NULL == dnsworker_contexts[idx]); 960275970Scy dnsworker_contexts[idx] = emalloc_zero(worker_context_sz); 961275970Scy} 962275970Scy 963275970Scy 964275970Scystatic dnsworker_ctx * 965275970Scyget_worker_context( 966275970Scy blocking_child * c, 967275970Scy u_int idx 968275970Scy ) 969275970Scy{ 970275970Scy static size_t ps = sizeof(dnsworker_contexts[0]); 971275970Scy u_int min_new_alloc; 972275970Scy u_int new_alloc; 973275970Scy size_t octets; 974275970Scy size_t new_octets; 975275970Scy 976275970Scy if (dnsworker_contexts_alloc <= idx) { 977275970Scy min_new_alloc = 1 + idx; 978275970Scy /* round new_alloc up to nearest multiple of 4 */ 979275970Scy new_alloc = (min_new_alloc + 4) & ~(4 - 1); 980275970Scy new_octets = new_alloc * ps; 981275970Scy octets = dnsworker_contexts_alloc * ps; 982275970Scy dnsworker_contexts = erealloc_zero(dnsworker_contexts, 983275970Scy new_octets, octets); 984275970Scy dnsworker_contexts_alloc = new_alloc; 985275970Scy } 986275970Scy 987275970Scy if (NULL == dnsworker_contexts[idx]) 988275970Scy alloc_dnsworker_context(idx); 989275970Scy ZERO(*dnsworker_contexts[idx]); 990275970Scy dnsworker_contexts[idx]->c = c; 991275970Scy 992275970Scy return dnsworker_contexts[idx]; 993275970Scy} 994275970Scy 995275970Scy 996275970Scystatic void 997275970Scyscheduled_sleep( 998275970Scy time_t scheduled, 999275970Scy time_t earliest, 1000275970Scy dnsworker_ctx * worker_ctx 1001275970Scy ) 1002275970Scy{ 1003275970Scy time_t now; 1004275970Scy 1005275970Scy if (scheduled < worker_ctx->ignore_scheduled_before) { 1006275970Scy TRACE(1, ("ignoring sleep until %s scheduled at %s (before %s)\n", 1007275970Scy humantime(earliest), humantime(scheduled), 1008275970Scy humantime(worker_ctx->ignore_scheduled_before))); 1009275970Scy return; 1010275970Scy } 1011275970Scy 1012275970Scy now = time(NULL); 1013275970Scy 1014275970Scy if (now < earliest) { 1015275970Scy TRACE(1, ("sleep until %s scheduled at %s (>= %s)\n", 1016275970Scy humantime(earliest), humantime(scheduled), 1017275970Scy humantime(worker_ctx->ignore_scheduled_before))); 1018275970Scy if (-1 == worker_sleep(worker_ctx->c, earliest - now)) { 1019275970Scy /* our sleep was interrupted */ 1020275970Scy now = time(NULL); 1021275970Scy worker_ctx->ignore_scheduled_before = now; 1022275970Scy#ifdef HAVE_RES_INIT 1023275970Scy worker_ctx->next_res_init = now + 60; 1024275970Scy next_res_init = worker_ctx->next_res_init; 1025275970Scy res_init(); 1026275970Scy#endif 1027275970Scy TRACE(1, ("sleep interrupted by daemon, ignoring sleeps scheduled before now (%s)\n", 1028275970Scy humantime(worker_ctx->ignore_scheduled_before))); 1029275970Scy } 1030275970Scy } 1031275970Scy} 1032275970Scy 1033275970Scy 1034275970Scy/* 1035275970Scy * manage_dns_retry_interval is a helper used by 1036275970Scy * getaddrinfo_sometime_complete and getnameinfo_sometime_complete 1037275970Scy * to calculate the new retry interval and schedule the next query. 1038275970Scy */ 1039275970Scystatic void 1040275970Scymanage_dns_retry_interval( 1041275970Scy time_t * pscheduled, 1042275970Scy time_t * pwhen, 1043275970Scy int * pretry, 1044275970Scy time_t * pnext_timeslot 1045275970Scy ) 1046275970Scy{ 1047275970Scy time_t now; 1048275970Scy time_t when; 1049275970Scy int retry; 1050275970Scy 1051275970Scy now = time(NULL); 1052275970Scy retry = *pretry; 1053275970Scy when = max(now + retry, *pnext_timeslot); 1054275970Scy *pnext_timeslot = when; 1055275970Scy retry = min(64, retry << 1); 1056275970Scy 1057275970Scy *pscheduled = now; 1058275970Scy *pwhen = when; 1059275970Scy *pretry = retry; 1060275970Scy} 1061275970Scy 1062275970Scy/* 1063275970Scy * should_retry_dns is a helper used by getaddrinfo_sometime_complete 1064275970Scy * and getnameinfo_sometime_complete which implements ntpd's DNS retry 1065275970Scy * policy. 1066275970Scy */ 1067275970Scystatic int 1068275970Scyshould_retry_dns( 1069275970Scy int rescode, 1070275970Scy int res_errno 1071275970Scy ) 1072275970Scy{ 1073275970Scy static int eai_again_seen; 1074275970Scy int again; 1075275970Scy#if defined (EAI_SYSTEM) && defined(DEBUG) 1076275970Scy char msg[256]; 1077275970Scy#endif 1078275970Scy 1079275970Scy /* 1080275970Scy * If the resolver failed, see if the failure is 1081275970Scy * temporary. If so, return success. 1082275970Scy */ 1083275970Scy again = 0; 1084275970Scy 1085275970Scy switch (rescode) { 1086275970Scy 1087275970Scy case EAI_FAIL: 1088275970Scy again = 1; 1089275970Scy break; 1090275970Scy 1091275970Scy case EAI_AGAIN: 1092275970Scy again = 1; 1093275970Scy eai_again_seen = 1; /* [Bug 1178] */ 1094275970Scy break; 1095275970Scy 1096275970Scy case EAI_NONAME: 1097275970Scy#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) 1098275970Scy case EAI_NODATA: 1099275970Scy#endif 1100275970Scy again = !eai_again_seen; /* [Bug 1178] */ 1101275970Scy break; 1102275970Scy 1103275970Scy#ifdef EAI_SYSTEM 1104275970Scy case EAI_SYSTEM: 1105275970Scy /* 1106275970Scy * EAI_SYSTEM means the real error is in errno. We should be more 1107275970Scy * discriminating about which errno values require retrying, but 1108275970Scy * this matches existing behavior. 1109275970Scy */ 1110275970Scy again = 1; 1111275970Scy# ifdef DEBUG 1112275970Scy errno_to_str(res_errno, msg, sizeof(msg)); 1113275970Scy TRACE(1, ("intres: EAI_SYSTEM errno %d (%s) means try again, right?\n", 1114275970Scy res_errno, msg)); 1115275970Scy# endif 1116275970Scy break; 1117275970Scy#endif 1118275970Scy } 1119275970Scy 1120275970Scy TRACE(2, ("intres: resolver returned: %s (%d), %sretrying\n", 1121275970Scy gai_strerror(rescode), rescode, again ? "" : "not ")); 1122275970Scy 1123275970Scy return again; 1124275970Scy} 1125275970Scy 1126275970Scy#else /* !WORKER follows */ 1127275970Scyint ntp_intres_nonempty_compilation_unit; 1128275970Scy#endif 1129