1 /* 2 * Copyright (C) 2006 Michael Brown <mbrown (at) fensystems.co.uk>. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License as 6 * published by the Free Software Foundation; either version 2 of the 7 * License, or any later version. 8 * 9 * This program is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 */ 18 19 FILE_LICENCE ( GPL2_OR_LATER ); 20 21 #include <string.h> 22 #include <stdio.h> 23 #include <errno.h> 24 #include <gpxe/netdevice.h> 25 #include <gpxe/dhcp.h> 26 #include <gpxe/settings.h> 27 #include <gpxe/image.h> 28 #include <gpxe/sanboot.h> 29 #include <gpxe/uri.h> 30 #include <usr/ifmgmt.h> 31 #include <usr/route.h> 32 #include <usr/dhcpmgmt.h> 33 #include <usr/imgmgmt.h> 34 #include <usr/autoboot.h> 35 36 /** @file 37 * 38 * Automatic booting 39 * 40 */ 41 42 /** Shutdown flags for exit */ 43 int shutdown_exit_flags = 0; 44 45 /** 46 * Identify the boot network device 47 * 48 * @ret netdev Boot network device 49 */ 50 static struct net_device * find_boot_netdev ( void ) { 51 return NULL; 52 } 53 54 /** 55 * Boot using next-server and filename 56 * 57 * @v filename Boot filename 58 * @ret rc Return status code 59 */ 60 int boot_next_server_and_filename ( struct in_addr next_server, 61 const char *filename ) { 62 struct uri *uri; 63 struct image *image; 64 char buf[ 23 /* tftp://xxx.xxx.xxx.xxx/ */ + 65 ( 3 * strlen(filename) ) /* completely URI-encoded */ 66 + 1 /* NUL */ ]; 67 int filename_is_absolute; 68 int rc; 69 70 /* Construct URI */ 71 uri = parse_uri ( filename ); 72 if ( ! uri ) 73 return -ENOMEM; 74 filename_is_absolute = uri_is_absolute ( uri ); 75 uri_put ( uri ); 76 if ( ! filename_is_absolute ) { 77 /* Construct a tftp:// URI for the filename. We can't 78 * just rely on the current working URI, because the 79 * relative URI resolution will remove the distinction 80 * between filenames with and without initial slashes, 81 * which is significant for TFTP. 82 */ 83 snprintf ( buf, sizeof ( buf ), "tftp://%s/", 84 inet_ntoa ( next_server ) ); 85 uri_encode ( filename, buf + strlen ( buf ), 86 sizeof ( buf ) - strlen ( buf ), URI_PATH ); 87 filename = buf; 88 } 89 90 image = alloc_image(); 91 if ( ! image ) 92 return -ENOMEM; 93 if ( ( rc = imgfetch ( image, filename, 94 register_and_autoload_image ) ) != 0 ) { 95 goto done; 96 } 97 if ( ( rc = imgexec ( image ) ) != 0 ) 98 goto done; 99 100 done: 101 image_put ( image ); 102 return rc; 103 } 104 105 /** 106 * Boot using root path 107 * 108 * @v root_path Root path 109 * @ret rc Return status code 110 */ 111 int boot_root_path ( const char *root_path ) { 112 struct sanboot_protocol *sanboot; 113 114 /* Quick hack */ 115 for_each_table_entry ( sanboot, SANBOOT_PROTOCOLS ) { 116 if ( strncmp ( root_path, sanboot->prefix, 117 strlen ( sanboot->prefix ) ) == 0 ) { 118 return sanboot->boot ( root_path ); 119 } 120 } 121 122 return -ENOTSUP; 123 } 124 125 /** 126 * Boot from a network device 127 * 128 * @v netdev Network device 129 * @ret rc Return status code 130 */ 131 static int netboot ( struct net_device *netdev ) { 132 struct setting vendor_class_id_setting 133 = { .tag = DHCP_VENDOR_CLASS_ID }; 134 struct setting pxe_discovery_control_setting 135 = { .tag = DHCP_PXE_DISCOVERY_CONTROL }; 136 struct setting pxe_boot_menu_setting 137 = { .tag = DHCP_PXE_BOOT_MENU }; 138 char buf[256]; 139 struct in_addr next_server; 140 unsigned int pxe_discovery_control; 141 int rc; 142 143 /* Open device and display device status */ 144 if ( ( rc = ifopen ( netdev ) ) != 0 ) 145 return rc; 146 ifstat ( netdev ); 147 148 /* Configure device via DHCP */ 149 if ( ( rc = dhcp ( netdev ) ) != 0 ) 150 return rc; 151 route(); 152 153 /* Try PXE menu boot, if applicable */ 154 fetch_string_setting ( NULL, &vendor_class_id_setting, 155 buf, sizeof ( buf ) ); 156 pxe_discovery_control = 157 fetch_uintz_setting ( NULL, &pxe_discovery_control_setting ); 158 if ( ( strcmp ( buf, "PXEClient" ) == 0 ) && pxe_menu_boot != NULL && 159 setting_exists ( NULL, &pxe_boot_menu_setting ) && 160 ( ! ( ( pxe_discovery_control & PXEBS_SKIP ) && 161 setting_exists ( NULL, &filename_setting ) ) ) ) { 162 printf ( "Booting from PXE menu\n" ); 163 return pxe_menu_boot ( netdev ); 164 } 165 166 /* Try to download and boot whatever we are given as a filename */ 167 fetch_ipv4_setting ( NULL, &next_server_setting, &next_server ); 168 fetch_string_setting ( NULL, &filename_setting, buf, sizeof ( buf ) ); 169 if ( buf[0] ) { 170 printf ( "Booting from filename \"%s\"\n", buf ); 171 if ( ( rc = boot_next_server_and_filename ( next_server, 172 buf ) ) != 0 ) { 173 printf ( "Could not boot from filename \"%s\": %s\n", 174 buf, strerror ( rc ) ); 175 return rc; 176 } 177 return 0; 178 } 179 180 /* No filename; try the root path */ 181 fetch_string_setting ( NULL, &root_path_setting, buf, sizeof ( buf ) ); 182 if ( buf[0] ) { 183 printf ( "Booting from root path \"%s\"\n", buf ); 184 if ( ( rc = boot_root_path ( buf ) ) != 0 ) { 185 printf ( "Could not boot from root path \"%s\": %s\n", 186 buf, strerror ( rc ) ); 187 return rc; 188 } 189 return 0; 190 } 191 192 printf ( "No filename or root path specified\n" ); 193 return -ENOENT; 194 } 195 196 /** 197 * Close all open net devices 198 * 199 * Called before a fresh boot attempt in order to free up memory. We 200 * don't just close the device immediately after the boot fails, 201 * because there may still be TCP connections in the process of 202 * closing. 203 */ 204 static void close_all_netdevs ( void ) { 205 struct net_device *netdev; 206 207 for_each_netdev ( netdev ) { 208 ifclose ( netdev ); 209 } 210 } 211 212 /** 213 * Boot the system 214 */ 215 void autoboot ( void ) { 216 struct net_device *boot_netdev; 217 struct net_device *netdev; 218 219 /* If we have an identifable boot device, try that first */ 220 close_all_netdevs(); 221 if ( ( boot_netdev = find_boot_netdev() ) ) 222 netboot ( boot_netdev ); 223 224 /* If that fails, try booting from any of the other devices */ 225 for_each_netdev ( netdev ) { 226 if ( netdev == boot_netdev ) 227 continue; 228 close_all_netdevs(); 229 netboot ( netdev ); 230 } 231 232 printf ( "No more network devices\n" ); 233 } 234