xref: /titanic_50/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c (revision 6a634c9dca3093f3922e4b7ab826d7bdf17bf78e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <stdlib.h>
29 #include <netinet/in.h>		/* struct in_addr */
30 #include <netinet/dhcp.h>
31 #include <signal.h>
32 #include <sys/socket.h>
33 #include <net/route.h>
34 #include <net/if_arp.h>
35 #include <string.h>
36 #include <dhcpmsg.h>
37 #include <ctype.h>
38 #include <netdb.h>
39 #include <fcntl.h>
40 #include <stdio.h>
41 #include <dhcp_hostconf.h>
42 
43 #include "states.h"
44 #include "agent.h"
45 #include "interface.h"
46 #include "util.h"
47 #include "packet.h"
48 
49 /*
50  * this file contains utility functions that have no real better home
51  * of their own.  they can largely be broken into six categories:
52  *
53  *  o  conversion functions -- functions to turn integers into strings,
54  *     or to convert between units of a similar measure.
55  *
56  *  o  time and timer functions -- functions to handle time measurement
57  *     and events.
58  *
59  *  o  ipc-related functions -- functions to simplify the generation of
60  *     ipc messages to the agent's clients.
61  *
62  *  o  signal-related functions -- functions to clean up the agent when
63  *     it receives a signal.
64  *
65  *  o  routing table manipulation functions
66  *
67  *  o  true miscellany -- anything else
68  */
69 
70 /*
71  * pkt_type_to_string(): stringifies a packet type
72  *
73  *   input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
74  *	    boolean_t: B_TRUE if IPv6
75  *  output: const char *: the stringified packet type
76  */
77 
78 const char *
pkt_type_to_string(uchar_t type,boolean_t isv6)79 pkt_type_to_string(uchar_t type, boolean_t isv6)
80 {
81 	/*
82 	 * note: the ordering in these arrays allows direct indexing of the
83 	 *	 table based on the RFC packet type value passed in.
84 	 */
85 
86 	static const char *v4types[] = {
87 		"BOOTP",  "DISCOVER", "OFFER",   "REQUEST", "DECLINE",
88 		"ACK",    "NAK",      "RELEASE", "INFORM"
89 	};
90 	static const char *v6types[] = {
91 		NULL, "SOLICIT", "ADVERTISE", "REQUEST",
92 		"CONFIRM", "RENEW", "REBIND", "REPLY",
93 		"RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST",
94 		"RELAY-FORW", "RELAY-REPL"
95 	};
96 
97 	if (isv6) {
98 		if (type >= sizeof (v6types) / sizeof (*v6types) ||
99 		    v6types[type] == NULL)
100 			return ("<unknown>");
101 		else
102 			return (v6types[type]);
103 	} else {
104 		if (type >= sizeof (v4types) / sizeof (*v4types) ||
105 		    v4types[type] == NULL)
106 			return ("<unknown>");
107 		else
108 			return (v4types[type]);
109 	}
110 }
111 
112 /*
113  * monosec_to_string(): converts a monosec_t into a date string
114  *
115  *   input: monosec_t: the monosec_t to convert
116  *  output: const char *: the corresponding date string
117  */
118 
119 const char *
monosec_to_string(monosec_t monosec)120 monosec_to_string(monosec_t monosec)
121 {
122 	time_t	time = monosec_to_time(monosec);
123 	char	*time_string = ctime(&time);
124 
125 	/* strip off the newline -- ugh, why, why, why.. */
126 	time_string[strlen(time_string) - 1] = '\0';
127 	return (time_string);
128 }
129 
130 /*
131  * monosec(): returns a monotonically increasing time in seconds that
132  *            is not affected by stime(2) or adjtime(2).
133  *
134  *   input: void
135  *  output: monosec_t: the number of seconds since some time in the past
136  */
137 
138 monosec_t
monosec(void)139 monosec(void)
140 {
141 	return (gethrtime() / NANOSEC);
142 }
143 
144 /*
145  * monosec_to_time(): converts a monosec_t into real wall time
146  *
147  *    input: monosec_t: the absolute monosec_t to convert
148  *   output: time_t: the absolute time that monosec_t represents in wall time
149  */
150 
151 time_t
monosec_to_time(monosec_t abs_monosec)152 monosec_to_time(monosec_t abs_monosec)
153 {
154 	return (abs_monosec - monosec()) + time(NULL);
155 }
156 
157 /*
158  * hrtime_to_monosec(): converts a hrtime_t to monosec_t
159  *
160  *    input: hrtime_t: the time to convert
161  *   output: monosec_t: the time in monosec_t
162  */
163 
164 monosec_t
hrtime_to_monosec(hrtime_t hrtime)165 hrtime_to_monosec(hrtime_t hrtime)
166 {
167 	return (hrtime / NANOSEC);
168 }
169 
170 /*
171  * print_server_msg(): prints a message from a DHCP server
172  *
173  *   input: dhcp_smach_t *: the state machine the message is associated with
174  *	    const char *: the string to display
175  *	    uint_t: length of string
176  *  output: void
177  */
178 
179 void
print_server_msg(dhcp_smach_t * dsmp,const char * msg,uint_t msglen)180 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen)
181 {
182 	if (msglen > 0) {
183 		dhcpmsg(MSG_INFO, "%s: message from server: %.*s",
184 		    dsmp->dsm_name, msglen, msg);
185 	}
186 }
187 
188 /*
189  * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
190  *
191  *    input: int: signal the handler was called with.
192  *
193  *   output: void
194  */
195 
196 static void
alrm_exit(int sig)197 alrm_exit(int sig)
198 {
199 	int exitval;
200 
201 	if (sig == SIGALRM && grandparent != 0)
202 		exitval = EXIT_SUCCESS;
203 	else
204 		exitval = EXIT_FAILURE;
205 
206 	_exit(exitval);
207 }
208 
209 /*
210  * daemonize(): daemonizes the process
211  *
212  *   input: void
213  *  output: int: 1 on success, 0 on failure
214  */
215 
216 int
daemonize(void)217 daemonize(void)
218 {
219 	/*
220 	 * We've found that adoption takes sufficiently long that
221 	 * a dhcpinfo run after dhcpagent -a is started may occur
222 	 * before the agent is ready to process the request.
223 	 * The result is an error message and an unhappy user.
224 	 *
225 	 * The initial process now sleeps for DHCP_ADOPT_SLEEP,
226 	 * unless interrupted by a SIGALRM, in which case it
227 	 * exits immediately. This has the effect that the
228 	 * grandparent doesn't exit until the dhcpagent is ready
229 	 * to process requests. This defers the the balance of
230 	 * the system start-up script processing until the
231 	 * dhcpagent is ready to field requests.
232 	 *
233 	 * grandparent is only set for the adopt case; other
234 	 * cases do not require the wait.
235 	 */
236 
237 	if (grandparent != 0)
238 		(void) signal(SIGALRM, alrm_exit);
239 
240 	switch (fork()) {
241 
242 	case -1:
243 		return (0);
244 
245 	case  0:
246 		if (grandparent != 0)
247 			(void) signal(SIGALRM, SIG_DFL);
248 
249 		/*
250 		 * setsid() makes us lose our controlling terminal,
251 		 * and become both a session leader and a process
252 		 * group leader.
253 		 */
254 
255 		(void) setsid();
256 
257 		/*
258 		 * under POSIX, a session leader can accidentally
259 		 * (through open(2)) acquire a controlling terminal if
260 		 * it does not have one.  just to be safe, fork again
261 		 * so we are not a session leader.
262 		 */
263 
264 		switch (fork()) {
265 
266 		case -1:
267 			return (0);
268 
269 		case 0:
270 			(void) signal(SIGHUP, SIG_IGN);
271 			(void) chdir("/");
272 			(void) umask(022);
273 			closefrom(0);
274 			break;
275 
276 		default:
277 			_exit(EXIT_SUCCESS);
278 		}
279 		break;
280 
281 	default:
282 		if (grandparent != 0) {
283 			(void) signal(SIGCHLD, SIG_IGN);
284 			/*
285 			 * Note that we're not the agent here, so the DHCP
286 			 * logging subsystem hasn't been configured yet.
287 			 */
288 			syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
289 			    "waiting for adoption to complete.");
290 			if (sleep(DHCP_ADOPT_SLEEP) == 0) {
291 				syslog(LOG_WARNING | LOG_DAEMON,
292 				    "dhcpagent: daemonize: timed out awaiting "
293 				    "adoption.");
294 			}
295 			syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
296 			    "wait finished");
297 		}
298 		_exit(EXIT_SUCCESS);
299 	}
300 
301 	return (1);
302 }
303 
304 /*
305  * update_default_route(): update the interface's default route
306  *
307  *   input: int: the type of message; either RTM_ADD or RTM_DELETE
308  *	    struct in_addr: the default gateway to use
309  *	    const char *: the interface associated with the route
310  *	    int: any additional flags (besides RTF_STATIC and RTF_GATEWAY)
311  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
312  */
313 
314 static boolean_t
update_default_route(uint32_t ifindex,int type,struct in_addr * gateway_nbo,int flags)315 update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo,
316     int flags)
317 {
318 	struct {
319 		struct rt_msghdr	rm_mh;
320 		struct sockaddr_in	rm_dst;
321 		struct sockaddr_in	rm_gw;
322 		struct sockaddr_in	rm_mask;
323 		struct sockaddr_dl	rm_ifp;
324 	} rtmsg;
325 
326 	(void) memset(&rtmsg, 0, sizeof (rtmsg));
327 	rtmsg.rm_mh.rtm_version = RTM_VERSION;
328 	rtmsg.rm_mh.rtm_msglen	= sizeof (rtmsg);
329 	rtmsg.rm_mh.rtm_type	= type;
330 	rtmsg.rm_mh.rtm_pid	= getpid();
331 	rtmsg.rm_mh.rtm_flags	= RTF_GATEWAY | RTF_STATIC | flags;
332 	rtmsg.rm_mh.rtm_addrs	= RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP;
333 
334 	rtmsg.rm_gw.sin_family	= AF_INET;
335 	rtmsg.rm_gw.sin_addr	= *gateway_nbo;
336 
337 	rtmsg.rm_dst.sin_family = AF_INET;
338 	rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
339 
340 	rtmsg.rm_mask.sin_family = AF_INET;
341 	rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
342 
343 	rtmsg.rm_ifp.sdl_family	= AF_LINK;
344 	rtmsg.rm_ifp.sdl_index	= ifindex;
345 
346 	return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
347 }
348 
349 /*
350  * add_default_route(): add the default route to the given gateway
351  *
352  *   input: const char *: the name of the interface associated with the route
353  *	    struct in_addr: the default gateway to add
354  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
355  */
356 
357 boolean_t
add_default_route(uint32_t ifindex,struct in_addr * gateway_nbo)358 add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
359 {
360 	return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP));
361 }
362 
363 /*
364  * del_default_route(): deletes the default route to the given gateway
365  *
366  *   input: const char *: the name of the interface associated with the route
367  *	    struct in_addr: if not INADDR_ANY, the default gateway to remove
368  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
369  */
370 
371 boolean_t
del_default_route(uint32_t ifindex,struct in_addr * gateway_nbo)372 del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
373 {
374 	if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
375 		return (B_TRUE);
376 
377 	return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0));
378 }
379 
380 /*
381  * inactivity_shutdown(): shuts down agent if there are no state machines left
382  *			  to manage
383  *
384  *   input: iu_tq_t *: unused
385  *	    void *: unused
386  *  output: void
387  */
388 
389 /* ARGSUSED */
390 void
inactivity_shutdown(iu_tq_t * tqp,void * arg)391 inactivity_shutdown(iu_tq_t *tqp, void *arg)
392 {
393 	if (smach_count() > 0)	/* shouldn't happen, but... */
394 		return;
395 
396 	dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
397 
398 	iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
399 }
400 
401 /*
402  * graceful_shutdown(): shuts down the agent gracefully
403  *
404  *   input: int: the signal that caused graceful_shutdown to be called
405  *  output: void
406  */
407 
408 void
graceful_shutdown(int sig)409 graceful_shutdown(int sig)
410 {
411 	iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
412 	    DHCP_REASON_SIGNAL), drain_script, NULL);
413 }
414 
415 /*
416  * bind_sock(): binds a socket to a given IP address and port number
417  *
418  *   input: int: the socket to bind
419  *	    in_port_t: the port number to bind to, host byte order
420  *	    in_addr_t: the address to bind to, host byte order
421  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
422  */
423 
424 boolean_t
bind_sock(int fd,in_port_t port_hbo,in_addr_t addr_hbo)425 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
426 {
427 	struct sockaddr_in	sin;
428 	int			on = 1;
429 
430 	(void) memset(&sin, 0, sizeof (struct sockaddr_in));
431 	sin.sin_family = AF_INET;
432 	sin.sin_port   = htons(port_hbo);
433 	sin.sin_addr.s_addr = htonl(addr_hbo);
434 
435 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
436 
437 	return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
438 }
439 
440 /*
441  * bind_sock_v6(): binds a socket to a given IP address and port number
442  *
443  *   input: int: the socket to bind
444  *	    in_port_t: the port number to bind to, host byte order
445  *	    in6_addr_t: the address to bind to, network byte order
446  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
447  */
448 
449 boolean_t
bind_sock_v6(int fd,in_port_t port_hbo,const in6_addr_t * addr_nbo)450 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
451 {
452 	struct sockaddr_in6	sin6;
453 	int			on = 1;
454 
455 	(void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
456 	sin6.sin6_family = AF_INET6;
457 	sin6.sin6_port   = htons(port_hbo);
458 	if (addr_nbo != NULL) {
459 		(void) memcpy(&sin6.sin6_addr, addr_nbo,
460 		    sizeof (sin6.sin6_addr));
461 	}
462 
463 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
464 
465 	return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
466 }
467 
468 /*
469  * valid_hostname(): check whether a string is a valid hostname
470  *
471  *   input: const char *: the string to verify as a hostname
472  *  output: boolean_t: B_TRUE if the string is a valid hostname
473  *
474  * Note that we accept both host names beginning with a digit and
475  * those containing hyphens.  Neither is strictly legal according
476  * to the RFCs, but both are in common practice, so we endeavour
477  * to not break what customers are using.
478  */
479 
480 static boolean_t
valid_hostname(const char * hostname)481 valid_hostname(const char *hostname)
482 {
483 	unsigned int i;
484 
485 	for (i = 0; hostname[i] != '\0'; i++) {
486 
487 		if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
488 		    (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
489 			continue;
490 
491 		return (B_FALSE);
492 	}
493 
494 	return (i > 0);
495 }
496 
497 /*
498  * iffile_to_hostname(): return the hostname contained on a line of the form
499  *
500  * [ ^I]*inet[ ^I]+hostname[\n]*\0
501  *
502  * in the file located at the specified path
503  *
504  *   input: const char *: the path of the file to look in for the hostname
505  *  output: const char *: the hostname at that path, or NULL on failure
506  */
507 
508 #define	IFLINE_MAX	1024	/* maximum length of a hostname.<if> line */
509 
510 const char *
iffile_to_hostname(const char * path)511 iffile_to_hostname(const char *path)
512 {
513 	FILE		*fp;
514 	static char	ifline[IFLINE_MAX];
515 
516 	fp = fopen(path, "r");
517 	if (fp == NULL)
518 		return (NULL);
519 
520 	/*
521 	 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
522 	 * such command is on a separate line (see the "while read ifcmds" code
523 	 * in /etc/init.d/inetinit).  Thus we will read the file a line at a
524 	 * time, searching for a line of the form
525 	 *
526 	 * [ ^I]*inet[ ^I]+hostname[\n]*\0
527 	 *
528 	 * extract the host name from it, and check it for validity.
529 	 */
530 	while (fgets(ifline, sizeof (ifline), fp) != NULL) {
531 		char *p;
532 
533 		if ((p = strstr(ifline, "inet")) != NULL) {
534 			if ((p != ifline) && !isspace(p[-1])) {
535 				(void) fclose(fp);
536 				return (NULL);
537 			}
538 			p += 4;	/* skip over "inet" and expect spaces or tabs */
539 			if ((*p == '\n') || (*p == '\0')) {
540 				(void) fclose(fp);
541 				return (NULL);
542 			}
543 			if (isspace(*p)) {
544 				char *nlptr;
545 
546 				/* no need to read more of the file */
547 				(void) fclose(fp);
548 
549 				while (isspace(*p))
550 					p++;
551 				if ((nlptr = strrchr(p, '\n')) != NULL)
552 					*nlptr = '\0';
553 				if (strlen(p) > MAXHOSTNAMELEN) {
554 					dhcpmsg(MSG_WARNING,
555 					    "iffile_to_hostname:"
556 					    " host name too long");
557 					return (NULL);
558 				}
559 				if (valid_hostname(p)) {
560 					return (p);
561 				} else {
562 					dhcpmsg(MSG_WARNING,
563 					    "iffile_to_hostname:"
564 					    " host name not valid");
565 					return (NULL);
566 				}
567 			} else {
568 				(void) fclose(fp);
569 				return (NULL);
570 			}
571 		}
572 	}
573 
574 	(void) fclose(fp);
575 	return (NULL);
576 }
577 
578 /*
579  * init_timer(): set up a DHCP timer
580  *
581  *   input: dhcp_timer_t *: the timer to set up
582  *  output: void
583  */
584 
585 void
init_timer(dhcp_timer_t * dt,lease_t startval)586 init_timer(dhcp_timer_t *dt, lease_t startval)
587 {
588 	dt->dt_id = -1;
589 	dt->dt_start = startval;
590 }
591 
592 /*
593  * cancel_timer(): cancel a DHCP timer
594  *
595  *   input: dhcp_timer_t *: the timer to cancel
596  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
597  */
598 
599 boolean_t
cancel_timer(dhcp_timer_t * dt)600 cancel_timer(dhcp_timer_t *dt)
601 {
602 	if (dt->dt_id == -1)
603 		return (B_TRUE);
604 
605 	if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
606 		dt->dt_id = -1;
607 		return (B_TRUE);
608 	}
609 
610 	return (B_FALSE);
611 }
612 
613 /*
614  * schedule_timer(): schedule a DHCP timer.  Note that it must not be already
615  *		     running, and that we can't cancel here.  If it were, and
616  *		     we did, we'd leak a reference to the callback argument.
617  *
618  *   input: dhcp_timer_t *: the timer to schedule
619  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
620  */
621 
622 boolean_t
schedule_timer(dhcp_timer_t * dt,iu_tq_callback_t * cbfunc,void * arg)623 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
624 {
625 	if (dt->dt_id != -1)
626 		return (B_FALSE);
627 	dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg);
628 	return (dt->dt_id != -1);
629 }
630 
631 /*
632  * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
633  *			 buffer.
634  *
635  *   input: const dhcpv6_option_t *: pointer to option
636  *	    uint_t: option length
637  *	    const char **: error string (nul-terminated)
638  *	    const char **: message from server (unterminated)
639  *	    uint_t *: length of server message
640  *  output: int: -1 on error, or >= 0 for a DHCPv6 status code
641  */
642 
643 int
dhcpv6_status_code(const dhcpv6_option_t * d6o,uint_t olen,const char ** estr,const char ** msg,uint_t * msglenp)644 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
645     const char **msg, uint_t *msglenp)
646 {
647 	uint16_t status;
648 	static const char *v6_status[] = {
649 		NULL,
650 		"Unknown reason",
651 		"Server has no addresses available",
652 		"Client record unavailable",
653 		"Prefix inappropriate for link",
654 		"Client must use multicast",
655 		"No prefix available"
656 	};
657 	static char sbuf[32];
658 
659 	*estr = "";
660 	*msg = "";
661 	*msglenp = 0;
662 	if (d6o == NULL)
663 		return (0);
664 	olen -= sizeof (*d6o);
665 	if (olen < 2) {
666 		*estr = "garbled status code";
667 		return (-1);
668 	}
669 
670 	*msg = (const char *)(d6o + 1) + 2;
671 	*msglenp = olen - 2;
672 
673 	(void) memcpy(&status, d6o + 1, sizeof (status));
674 	status = ntohs(status);
675 	if (status > 0) {
676 		if (status > DHCPV6_STAT_NOPREFIX) {
677 			(void) snprintf(sbuf, sizeof (sbuf), "status %u",
678 			    status);
679 			*estr = sbuf;
680 		} else {
681 			*estr = v6_status[status];
682 		}
683 	}
684 	return (status);
685 }
686 
687 void
write_lease_to_hostconf(dhcp_smach_t * dsmp)688 write_lease_to_hostconf(dhcp_smach_t *dsmp)
689 {
690 	PKT_LIST *plp[2];
691 	const char *hcfile;
692 
693 	hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
694 	plp[0] = dsmp->dsm_ack;
695 	plp[1] = dsmp->dsm_orig_ack;
696 	if (write_hostconf(dsmp->dsm_name, plp, 2,
697 	    monosec_to_time(dsmp->dsm_curstart_monosec),
698 	    dsmp->dsm_isv6) != -1) {
699 		dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
700 	} else if (errno == EROFS) {
701 		dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
702 		    "system; not saving lease", hcfile);
703 	} else {
704 		dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
705 		    "not use cached configuration)", hcfile);
706 	}
707 }
708