1 /*
2 * Copyright (c) 2001-2019 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 /*
30 * History:
31 * 14 December, 2001 Dieter Siegmund ([email protected])
32 * - created
33 */
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/kernel.h>
37 #include <sys/conf.h>
38 #include <sys/ioctl.h>
39 #include <sys/proc_internal.h>
40 #include <sys/mount_internal.h>
41 #include <sys/mbuf.h>
42 #include <sys/filedesc.h>
43 #include <sys/vnode_internal.h>
44 #include <sys/malloc.h>
45 #include <sys/socket.h>
46 #include <sys/socketvar.h>
47 #include <sys/reboot.h>
48 #include <sys/kauth.h>
49 #include <net/if.h>
50 #include <net/if_dl.h>
51 #include <net/if_types.h>
52 #include <net/route.h>
53 #include <netinet/in.h>
54 #include <netinet/if_ether.h>
55 #include <netinet/dhcp_options.h>
56
57 #include <kern/kern_types.h>
58 #include <kern/kalloc.h>
59 #include <sys/netboot.h>
60 #include <sys/imageboot.h>
61 #include <pexpert/pexpert.h>
62
63 extern int (*mountroot)(void);
64
65 extern unsigned char rootdevice[];
66
67 static int S_netboot = 0;
68 static struct netboot_info * S_netboot_info_p;
69
70 void *
71 IOBSDRegistryEntryForDeviceTree(const char * path);
72
73 void
74 IOBSDRegistryEntryRelease(void * entry);
75
76 const void *
77 IOBSDRegistryEntryGetData(void * entry, const char * property_name,
78 int * packet_length);
79
80 #define BOOTP_RESPONSE "bootp-response"
81 #define BSDP_RESPONSE "bsdp-response"
82 #define DHCP_RESPONSE "dhcp-response"
83
84 #define IP_FORMAT "%d.%d.%d.%d"
85 #define IP_CH(ip) ((u_char *)ip)
86 #define IP_LIST(ip) IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3]
87
88 #define kNetBootRootPathPrefixNFS "nfs:"
89 #define kNetBootRootPathPrefixHTTP "http:"
90
91 typedef enum {
92 kNetBootImageTypeUnknown = 0,
93 kNetBootImageTypeNFS = 1, // Deprecated
94 kNetBootImageTypeHTTP = 2,
95 } NetBootImageType;
96
97 struct netboot_info {
98 struct in_addr client_ip;
99 struct in_addr server_ip;
100 char * server_name;
101 size_t server_name_length;
102 char * mount_point;
103 size_t mount_point_length;
104 char * image_path;
105 size_t image_path_length;
106 NetBootImageType image_type;
107 char * second_image_path;
108 size_t second_image_path_length;
109 };
110
111 /*
112 * Function: parse_booter_path
113 * Purpose:
114 * Parse a string of the form:
115 * "<IP>:<host>:<mount>[:<image_path>]"
116 * into the given ip address, host, mount point, and optionally, image_path.
117 *
118 * Note:
119 * The passed in string is modified i.e. ':' is replaced by '\0'.
120 * Example:
121 * "17.202.16.17:seaport:/release/.images/Image9/CurrentHera"
122 */
123 static __inline__ boolean_t
parse_booter_path(char * path,struct in_addr * iaddr_p,char const ** host,char ** mount_dir,char ** image_path)124 parse_booter_path(char * path, struct in_addr * iaddr_p, char const * * host,
125 char * * mount_dir, char * * image_path)
126 {
127 char * start;
128 char * colon;
129
130 /* IP address */
131 start = path;
132 colon = strchr(start, ':');
133 if (colon == NULL) {
134 return FALSE;
135 }
136 *colon = '\0';
137 if (inet_aton(start, iaddr_p) != 1) {
138 return FALSE;
139 }
140
141 /* host */
142 start = colon + 1;
143 colon = strchr(start, ':');
144 if (colon == NULL) {
145 return FALSE;
146 }
147 *colon = '\0';
148 *host = start;
149
150 /* mount */
151 start = colon + 1;
152 colon = strchr(start, ':');
153 *mount_dir = start;
154 if (colon == NULL) {
155 *image_path = NULL;
156 } else {
157 /* image path */
158 *colon = '\0';
159 start = colon + 1;
160 *image_path = start;
161 }
162 return TRUE;
163 }
164
165 /*
166 * Function: find_colon
167 * Purpose:
168 * Find the next unescaped instance of the colon character.
169 * If a colon is escaped (preceded by a backslash '\' character),
170 * shift the string over by one character to overwrite the backslash.
171 */
172 static __inline__ char *
find_colon(char * str)173 find_colon(char * str)
174 {
175 char * start = str;
176 char * colon;
177
178 while ((colon = strchr(start, ':')) != NULL) {
179 char * dst;
180 char * src;
181
182 if (colon == start) {
183 break;
184 }
185 if (colon[-1] != '\\') {
186 break;
187 }
188 for (dst = colon - 1, src = colon; *dst != '\0'; dst++, src++) {
189 *dst = *src;
190 }
191 start = colon;
192 }
193 return colon;
194 }
195
196 /*
197 * Function: parse_netboot_path
198 * Purpose:
199 * Parse a string of the form:
200 * "nfs:<IP>:<mount>[:<image_path>]"
201 * into the given ip address, host, mount point, and optionally, image_path.
202 * Notes:
203 * - the passed in string is modified i.e. ':' is replaced by '\0'
204 * - literal colons must be escaped with a backslash
205 *
206 * Examples:
207 * nfs:17.202.42.112:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
208 * nfs:17.202.42.112:/Volumes/Foo\:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
209 */
210 static __inline__ boolean_t
parse_netboot_path(char * path,struct in_addr * iaddr_p,char const ** host,char ** mount_dir,char ** image_path)211 parse_netboot_path(char * path, struct in_addr * iaddr_p, char const * * host,
212 char * * mount_dir, char * * image_path)
213 {
214 static char tmp[MAX_IPv4_STR_LEN]; /* Danger - not thread safe */
215 char * start;
216 char * colon;
217
218 if (strncmp(path, kNetBootRootPathPrefixNFS,
219 strlen(kNetBootRootPathPrefixNFS)) != 0) {
220 return FALSE;
221 }
222
223 /* IP address */
224 start = path + strlen(kNetBootRootPathPrefixNFS);
225 colon = strchr(start, ':');
226 if (colon == NULL) {
227 return FALSE;
228 }
229 *colon = '\0';
230 if (inet_aton(start, iaddr_p) != 1) {
231 return FALSE;
232 }
233
234 /* mount point */
235 start = colon + 1;
236 colon = find_colon(start);
237 *mount_dir = start;
238 if (colon == NULL) {
239 *image_path = NULL;
240 } else {
241 /* image path */
242 *colon = '\0';
243 start = colon + 1;
244 (void)find_colon(start);
245 *image_path = start;
246 }
247 *host = inet_ntop(AF_INET, iaddr_p, tmp, sizeof(tmp));
248 return TRUE;
249 }
250
251 static boolean_t
parse_image_path(char * path,struct in_addr * iaddr_p,char const ** host,char ** mount_dir,char ** image_path)252 parse_image_path(char * path, struct in_addr * iaddr_p, char const * * host,
253 char * * mount_dir, char * * image_path)
254 {
255 if (path[0] >= '0' && path[0] <= '9') {
256 return parse_booter_path(path, iaddr_p, host, mount_dir,
257 image_path);
258 }
259 return parse_netboot_path(path, iaddr_p, host, mount_dir,
260 image_path);
261 }
262
263 static boolean_t
get_root_path(char * root_path)264 get_root_path(char * root_path)
265 {
266 void * entry;
267 boolean_t found = FALSE;
268 const void * pkt;
269 int pkt_len;
270
271 entry = IOBSDRegistryEntryForDeviceTree("/chosen");
272 if (entry == NULL) {
273 return FALSE;
274 }
275 pkt = IOBSDRegistryEntryGetData(entry, BSDP_RESPONSE, &pkt_len);
276 if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
277 printf("netboot: retrieving root path from BSDP response\n");
278 } else {
279 pkt = IOBSDRegistryEntryGetData(entry, BOOTP_RESPONSE,
280 &pkt_len);
281 if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
282 printf("netboot: retrieving root path from BOOTP response\n");
283 }
284 }
285 if (pkt != NULL) {
286 int len;
287 dhcpol_t options;
288 const char * path;
289 const struct dhcp * reply;
290
291 reply = (const struct dhcp *)pkt;
292 (void)dhcpol_parse_packet(&options, reply, pkt_len);
293
294 path = (const char *)dhcpol_find(&options,
295 dhcptag_root_path_e, &len, NULL);
296 if (path) {
297 memcpy(root_path, path, len);
298 root_path[len] = '\0';
299 found = TRUE;
300 }
301 }
302 IOBSDRegistryEntryRelease(entry);
303 return found;
304 }
305
306 static void
save_path(char ** str_p,size_t * length_p,char * path)307 save_path(char * * str_p, size_t * length_p, char * path)
308 {
309 *length_p = strlen(path) + 1;
310 *str_p = kalloc_data(*length_p, Z_WAITOK);
311 strlcpy(*str_p, path, *length_p);
312 return;
313 }
314
315 static struct netboot_info *
netboot_info_init(struct in_addr iaddr)316 netboot_info_init(struct in_addr iaddr)
317 {
318 boolean_t have_root_path = FALSE;
319 struct netboot_info * info = NULL;
320 char * root_path = NULL;
321
322 info = (struct netboot_info *)kalloc_type(struct netboot_info, Z_WAITOK | Z_ZERO);
323 info->client_ip = iaddr;
324 info->image_type = kNetBootImageTypeUnknown;
325
326 /* check for a booter-specified path then a NetBoot path */
327 root_path = zalloc(ZV_NAMEI);
328
329 if (PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == TRUE
330 || PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == TRUE
331 || PE_parse_boot_argn("rootpath", root_path, MAXPATHLEN) == TRUE) {
332 if (imageboot_format_is_valid(root_path)) {
333 printf("netboot_info_init: rp0='%s' isn't a network path,"
334 " ignoring\n", root_path);
335 } else {
336 have_root_path = TRUE;
337 }
338 }
339 if (have_root_path == FALSE) {
340 have_root_path = get_root_path(root_path);
341 }
342 if (have_root_path) {
343 const char * server_name = NULL;
344 char * mount_point = NULL;
345 char * image_path = NULL;
346 struct in_addr server_ip;
347
348 if (parse_image_path(root_path, &server_ip, &server_name,
349 &mount_point, &image_path)) {
350 /* kNetBootImageTypeNFS is deprecated */
351 printf("netboot: NFS boot is deprecated\n");
352 } else if (strncmp(root_path, kNetBootRootPathPrefixHTTP,
353 strlen(kNetBootRootPathPrefixHTTP)) == 0) {
354 info->image_type = kNetBootImageTypeHTTP;
355 save_path(&info->image_path, &info->image_path_length,
356 root_path);
357 printf("netboot: HTTP URL %s\n", info->image_path);
358 } else {
359 printf("netboot: root path uses unrecognized format\n");
360 }
361
362 /* check for image-within-image */
363 if (info->image_path != NULL) {
364 if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN)
365 || PE_parse_boot_argn("rp1", root_path, MAXPATHLEN)) {
366 /* rp1/root-dmg is the second-level image */
367 save_path(&info->second_image_path, &info->second_image_path_length,
368 root_path);
369 }
370 }
371 if (info->second_image_path != NULL) {
372 printf("netboot: nested image %s\n", info->second_image_path);
373 }
374 }
375 zfree(ZV_NAMEI, root_path);
376 return info;
377 }
378
379 static void
netboot_info_free(struct netboot_info ** info_p)380 netboot_info_free(struct netboot_info * * info_p)
381 {
382 struct netboot_info * info = *info_p;
383
384 if (info) {
385 kfree_data(info->mount_point, info->mount_point_length);
386 kfree_data(info->server_name, info->server_name_length);
387 kfree_data(info->image_path, info->image_path_length);
388 kfree_data(info->second_image_path,
389 info->second_image_path_length);
390 kfree_type(struct netboot_info, info);
391 }
392 *info_p = NULL;
393 }
394
395 boolean_t
netboot_iaddr(struct in_addr * iaddr_p)396 netboot_iaddr(struct in_addr * iaddr_p)
397 {
398 if (S_netboot_info_p == NULL) {
399 return FALSE;
400 }
401
402 *iaddr_p = S_netboot_info_p->client_ip;
403 return TRUE;
404 }
405
406 boolean_t
netboot_rootpath(struct in_addr * server_ip,char * name,size_t name_len,char * path,size_t path_len)407 netboot_rootpath(struct in_addr * server_ip,
408 char * name, size_t name_len,
409 char * path, size_t path_len)
410 {
411 if (S_netboot_info_p == NULL) {
412 return FALSE;
413 }
414
415 name[0] = '\0';
416 path[0] = '\0';
417
418 if (S_netboot_info_p->mount_point_length == 0) {
419 return FALSE;
420 }
421 if (path_len < S_netboot_info_p->mount_point_length) {
422 printf("netboot: path too small %zu < %zu\n",
423 path_len, S_netboot_info_p->mount_point_length);
424 return FALSE;
425 }
426 strlcpy(path, S_netboot_info_p->mount_point, path_len);
427 strlcpy(name, S_netboot_info_p->server_name, name_len);
428 *server_ip = S_netboot_info_p->server_ip;
429 return TRUE;
430 }
431
432
433 static boolean_t
get_ip_parameters(struct in_addr * iaddr_p,struct in_addr * netmask_p,struct in_addr * router_p)434 get_ip_parameters(struct in_addr * iaddr_p, struct in_addr * netmask_p,
435 struct in_addr * router_p)
436 {
437 void * entry;
438 const void * pkt;
439 int pkt_len;
440
441
442 entry = IOBSDRegistryEntryForDeviceTree("/chosen");
443 if (entry == NULL) {
444 return FALSE;
445 }
446 pkt = IOBSDRegistryEntryGetData(entry, DHCP_RESPONSE, &pkt_len);
447 if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
448 printf("netboot: retrieving IP information from DHCP response\n");
449 } else {
450 pkt = IOBSDRegistryEntryGetData(entry, BOOTP_RESPONSE, &pkt_len);
451 if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
452 printf("netboot: retrieving IP information from BOOTP response\n");
453 }
454 }
455 if (pkt != NULL) {
456 const struct in_addr * ip;
457 int len;
458 dhcpol_t options;
459 const struct dhcp * reply;
460
461 reply = (const struct dhcp *)pkt;
462 (void)dhcpol_parse_packet(&options, reply, pkt_len);
463 *iaddr_p = reply->dp_yiaddr;
464 ip = (const struct in_addr *)
465 dhcpol_find(&options,
466 dhcptag_subnet_mask_e, &len, NULL);
467 if (ip) {
468 *netmask_p = *ip;
469 }
470 ip = (const struct in_addr *)
471 dhcpol_find(&options, dhcptag_router_e, &len, NULL);
472 if (ip) {
473 *router_p = *ip;
474 }
475 }
476 IOBSDRegistryEntryRelease(entry);
477 return pkt != NULL;
478 }
479
480 static int
route_cmd(int cmd,struct in_addr d,struct in_addr g,struct in_addr m,uint32_t more_flags,unsigned int ifscope)481 route_cmd(int cmd, struct in_addr d, struct in_addr g,
482 struct in_addr m, uint32_t more_flags, unsigned int ifscope)
483 {
484 struct sockaddr_in dst;
485 int error;
486 uint32_t flags = RTF_UP | RTF_STATIC;
487 struct sockaddr_in gw;
488 struct sockaddr_in mask;
489
490 flags |= more_flags;
491
492 /* destination */
493 bzero((caddr_t)&dst, sizeof(dst));
494 dst.sin_len = sizeof(dst);
495 dst.sin_family = AF_INET;
496 dst.sin_addr = d;
497
498 /* gateway */
499 bzero((caddr_t)&gw, sizeof(gw));
500 gw.sin_len = sizeof(gw);
501 gw.sin_family = AF_INET;
502 gw.sin_addr = g;
503
504 /* mask */
505 bzero(&mask, sizeof(mask));
506 mask.sin_len = sizeof(mask);
507 mask.sin_family = AF_INET;
508 mask.sin_addr = m;
509
510 error = rtrequest_scoped(cmd, (struct sockaddr *)&dst,
511 (struct sockaddr *)&gw, (struct sockaddr *)&mask, flags, NULL, ifscope);
512
513 return error;
514 }
515
516 static int
default_route_add(struct in_addr router,boolean_t proxy_arp)517 default_route_add(struct in_addr router, boolean_t proxy_arp)
518 {
519 uint32_t flags = 0;
520 struct in_addr zeroes = { .s_addr = 0 };
521
522 if (proxy_arp == FALSE) {
523 flags |= RTF_GATEWAY;
524 }
525 return route_cmd(RTM_ADD, zeroes, router, zeroes, flags, IFSCOPE_NONE);
526 }
527
528 static struct ifnet *
find_interface(void)529 find_interface(void)
530 {
531 struct ifnet * ifp = NULL;
532
533 dlil_if_lock();
534 if (rootdevice[0]) {
535 ifp = ifunit((char *)rootdevice);
536 }
537 if (ifp == NULL) {
538 ifnet_head_lock_shared();
539 TAILQ_FOREACH(ifp, &ifnet_head, if_link)
540 if ((ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) == 0) {
541 break;
542 }
543 ifnet_head_done();
544 }
545 dlil_if_unlock();
546 return ifp;
547 }
548
549 static const struct sockaddr_in blank_sin = {
550 .sin_len = sizeof(struct sockaddr_in),
551 .sin_family = AF_INET,
552 .sin_port = 0,
553 .sin_addr = { .s_addr = 0 },
554 .sin_zero = { 0, 0, 0, 0, 0, 0, 0, 0 }
555 };
556
557 static int
inet_aifaddr(struct socket * so,const char * name,const struct in_addr * addr,const struct in_addr * mask,const struct in_addr * broadcast)558 inet_aifaddr(struct socket * so, const char * name,
559 const struct in_addr * addr,
560 const struct in_addr * mask,
561 const struct in_addr * broadcast)
562 {
563 struct ifaliasreq ifra;
564
565 bzero(&ifra, sizeof(ifra));
566 strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name));
567 if (addr) {
568 *((struct sockaddr_in *)(void *)&ifra.ifra_addr) = blank_sin;
569 ((struct sockaddr_in *)(void *)&ifra.ifra_addr)->sin_addr = *addr;
570 }
571 if (mask) {
572 *((struct sockaddr_in *)(void *)&ifra.ifra_mask) = blank_sin;
573 ((struct sockaddr_in *)(void *)&ifra.ifra_mask)->sin_addr = *mask;
574 }
575 if (broadcast) {
576 *((struct sockaddr_in *)(void *)&ifra.ifra_broadaddr) = blank_sin;
577 ((struct sockaddr_in *)(void *)&ifra.ifra_broadaddr)->sin_addr = *broadcast;
578 }
579 return ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, current_proc());
580 }
581
582
583 int
netboot_mountroot(void)584 netboot_mountroot(void)
585 {
586 int error = 0;
587 struct in_addr iaddr = { .s_addr = 0 };
588 struct ifreq ifr;
589 struct ifnet * ifp;
590 struct in_addr netmask = { .s_addr = 0 };
591 proc_t procp = current_proc();
592 struct in_addr router = { .s_addr = 0 };
593 struct socket * so = NULL;
594
595 bzero(&ifr, sizeof(ifr));
596
597 /* find the interface */
598 ifp = find_interface();
599 if (ifp == NULL) {
600 printf("netboot: no suitable interface\n");
601 error = ENXIO;
602 goto failed;
603 }
604 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", if_name(ifp));
605 printf("netboot: using network interface '%s'\n", ifr.ifr_name);
606
607 /* bring it up */
608 if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0) {
609 printf("netboot: socreate, error=%d\n", error);
610 goto failed;
611 }
612 ifr.ifr_flags = ifp->if_flags | IFF_UP;
613 error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ifr, procp);
614 if (error) {
615 printf("netboot: SIFFLAGS, error=%d\n", error);
616 goto failed;
617 }
618
619 /* grab information from the registry */
620 if (get_ip_parameters(&iaddr, &netmask, &router) == FALSE) {
621 printf("netboot: can't retrieve IP parameters\n");
622 goto failed;
623 }
624 OS_ANALYZER_SUPPRESS("12641116") printf("netboot: IP address " IP_FORMAT, IP_LIST(&iaddr));
625 if (netmask.s_addr) {
626 printf(" netmask " IP_FORMAT, IP_LIST(&netmask));
627 }
628 if (router.s_addr) {
629 printf(" router " IP_FORMAT, IP_LIST(&router));
630 }
631 printf("\n");
632 error = inet_aifaddr(so, ifr.ifr_name, &iaddr, &netmask, NULL);
633 if (error) {
634 printf("netboot: inet_aifaddr failed, %d\n", error);
635 goto failed;
636 }
637 if (router.s_addr == 0) {
638 /* enable proxy arp if we don't have a router */
639 router.s_addr = iaddr.s_addr;
640 }
641 printf("netboot: adding default route " IP_FORMAT "\n",
642 IP_LIST(&router));
643 error = default_route_add(router, router.s_addr == iaddr.s_addr);
644 if (error) {
645 printf("netboot: default_route_add failed %d\n", error);
646 }
647
648 soclose(so);
649
650 S_netboot_info_p = netboot_info_init(iaddr);
651 switch (S_netboot_info_p->image_type) {
652 default:
653 case kNetBootImageTypeNFS:
654 /* kNetBootImageTypeNFS is deprecated */
655 error = ENOTSUP;
656 break;
657 case kNetBootImageTypeHTTP:
658 error = netboot_setup();
659 break;
660 }
661 if (error == 0) {
662 S_netboot = 1;
663 } else {
664 S_netboot = 0;
665 }
666 return error;
667 failed:
668 if (so != NULL) {
669 soclose(so);
670 }
671 return error;
672 }
673
674 int
netboot_setup(void)675 netboot_setup(void)
676 {
677 int error = 0;
678
679 if (S_netboot_info_p == NULL
680 || S_netboot_info_p->image_path == NULL) {
681 goto done;
682 }
683 printf("netboot_setup: calling imageboot_mount_image\n");
684 error = imageboot_mount_image(S_netboot_info_p->image_path, -1, IMAGEBOOT_DMG);
685 if (error != 0) {
686 printf("netboot: failed to mount root image, %d\n", error);
687 } else if (S_netboot_info_p->second_image_path != NULL) {
688 error = imageboot_mount_image(S_netboot_info_p->second_image_path, 0, IMAGEBOOT_DMG);
689 if (error != 0) {
690 printf("netboot: failed to mount second root image, %d\n", error);
691 }
692 }
693
694 done:
695 netboot_info_free(&S_netboot_info_p);
696 return error;
697 }
698
699 int
netboot_root(void)700 netboot_root(void)
701 {
702 return S_netboot;
703 }
704