Skip to content
Snippets Groups Projects
cmd_pxe.c 34.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • static int parse_menu(cmd_tbl_t *cmdtp, char **c, struct pxe_menu *cfg, char *b, int nest_level)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    {
    	struct token t;
    	char *s = *c;
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    	get_token(c, &t, L_KEYWORD);
    
    	switch (t.type) {
    	case T_TITLE:
    		err = parse_sliteral(c, &cfg->title);
    
    		break;
    
    	case T_INCLUDE:
    
    		err = handle_include(cmdtp, c, b + strlen(b) + 1, cfg,
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    						nest_level + 1);
    		break;
    
    	default:
    		printf("Ignoring malformed menu command: %.*s\n",
    				(int)(*c - s), s);
    	}
    
    	if (err < 0)
    		return err;
    
    	eol_or_eof(c);
    
    	return 1;
    }
    
    /*
     * Handles parsing a 'menu line' when we're parsing a label.
     */
    static int parse_label_menu(char **c, struct pxe_menu *cfg,
    				struct pxe_label *label)
    {
    	struct token t;
    	char *s;
    
    	s = *c;
    
    	get_token(c, &t, L_KEYWORD);
    
    	switch (t.type) {
    	case T_DEFAULT:
    
    		if (!cfg->default_label)
    			cfg->default_label = strdup(label->name);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    		if (!cfg->default_label)
    			return -ENOMEM;
    
    
    		break;
    	case T_LABEL:
    		parse_sliteral(c, &label->menu);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		break;
    	default:
    		printf("Ignoring malformed menu command: %.*s\n",
    				(int)(*c - s), s);
    	}
    
    	eol_or_eof(c);
    
    	return 0;
    }
    
    /*
     * Parses a label and adds it to the list of labels for a menu.
     *
     * A label ends when we either get to the end of a file, or
     * get some input we otherwise don't have a handler defined
     * for.
     *
     */
    static int parse_label(char **c, struct pxe_menu *cfg)
    {
    	struct token t;
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	char *s = *c;
    	struct pxe_label *label;
    	int err;
    
    	label = label_create();
    	if (!label)
    		return -ENOMEM;
    
    	err = parse_sliteral(c, &label->name);
    	if (err < 0) {
    		printf("Expected label name: %.*s\n", (int)(*c - s), s);
    		label_destroy(label);
    		return -EINVAL;
    	}
    
    	list_add_tail(&label->list, &cfg->labels);
    
    	while (1) {
    		s = *c;
    		get_token(c, &t, L_KEYWORD);
    
    		err = 0;
    		switch (t.type) {
    		case T_MENU:
    			err = parse_label_menu(c, cfg, label);
    			break;
    
    		case T_KERNEL:
    
    		case T_LINUX:
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			err = parse_sliteral(c, &label->kernel);
    			break;
    
    		case T_APPEND:
    			err = parse_sliteral(c, &label->append);
    
    			if (label->initrd)
    				break;
    			s = strstr(label->append, "initrd=");
    			if (!s)
    				break;
    			s += 7;
    			len = (int)(strchr(s, ' ') - s);
    			label->initrd = malloc(len + 1);
    			strncpy(label->initrd, s, len);
    			label->initrd[len] = '\0';
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			break;
    
    		case T_INITRD:
    
    			if (!label->initrd)
    				err = parse_sliteral(c, &label->initrd);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			break;
    
    
    		case T_FDT:
    			if (!label->fdt)
    				err = parse_sliteral(c, &label->fdt);
    			break;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		case T_LOCALBOOT:
    
    			label->localboot = 1;
    			err = parse_integer(c, &label->localboot_val);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			break;
    
    
    Rob Herring's avatar
    Rob Herring committed
    		case T_IPAPPEND:
    			err = parse_integer(c, &label->ipappend);
    			break;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		case T_EOL:
    			break;
    
    		default:
    			/*
    			 * put the token back! we don't want it - it's the end
    			 * of a label and whatever token this is, it's
    			 * something for the menu level context to handle.
    			 */
    			*c = s;
    			return 1;
    		}
    
    		if (err < 0)
    			return err;
    	}
    }
    
    /*
     * This 16 comes from the limit pxelinux imposes on nested includes.
     *
     * There is no reason at all we couldn't do more, but some limit helps prevent
     * infinite (until crash occurs) recursion if a file tries to include itself.
     */
    #define MAX_NEST_LEVEL 16
    
    /*
     * Entry point for parsing a menu file. nest_level indicates how many times
     * we've nested in includes.  It will be 1 for the top level menu file.
     *
     * Returns 1 on success, < 0 on error.
     */
    
    static int parse_pxefile_top(cmd_tbl_t *cmdtp, char *p, struct pxe_menu *cfg, int nest_level)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    {
    	struct token t;
    	char *s, *b, *label_name;
    	int err;
    
    	b = p;
    
    	if (nest_level > MAX_NEST_LEVEL) {
    		printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL);
    		return -EMLINK;
    	}
    
    	while (1) {
    		s = p;
    
    		get_token(&p, &t, L_KEYWORD);
    
    		err = 0;
    		switch (t.type) {
    		case T_MENU:
    
    			cfg->prompt = 1;
    
    			err = parse_menu(cmdtp, &p, cfg, b, nest_level);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			break;
    
    		case T_TIMEOUT:
    			err = parse_integer(&p, &cfg->timeout);
    			break;
    
    		case T_LABEL:
    			err = parse_label(&p, cfg);
    			break;
    
    		case T_DEFAULT:
    
    		case T_ONTIMEOUT:
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			err = parse_sliteral(&p, &label_name);
    
    			if (label_name) {
    				if (cfg->default_label)
    					free(cfg->default_label);
    
    				cfg->default_label = label_name;
    			}
    
    			break;
    
    
    		case T_INCLUDE:
    
    			err = handle_include(cmdtp, &p, b + ALIGN(strlen(b), 4), cfg,
    
    							nest_level + 1);
    			break;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		case T_PROMPT:
    
    			eol_or_eof(&p);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			break;
    
    		case T_EOL:
    			break;
    
    		case T_EOF:
    			return 1;
    
    		default:
    			printf("Ignoring unknown command: %.*s\n",
    							(int)(p - s), s);
    			eol_or_eof(&p);
    		}
    
    		if (err < 0)
    			return err;
    	}
    }
    
    /*
     * Free the memory used by a pxe_menu and its labels.
     */
    static void destroy_pxe_menu(struct pxe_menu *cfg)
    {
    	struct list_head *pos, *n;
    	struct pxe_label *label;
    
    	if (cfg->title)
    		free(cfg->title);
    
    	if (cfg->default_label)
    		free(cfg->default_label);
    
    	list_for_each_safe(pos, n, &cfg->labels) {
    		label = list_entry(pos, struct pxe_label, list);
    
    		label_destroy(label);
    	}
    
    	free(cfg);
    }
    
    /*
     * Entry point for parsing a pxe file. This is only used for the top level
     * file.
     *
     * Returns NULL if there is an error, otherwise, returns a pointer to a
     * pxe_menu struct populated with the results of parsing the pxe file (and any
     * files it includes). The resulting pxe_menu struct can be free()'d by using
     * the destroy_pxe_menu() function.
     */
    
    static struct pxe_menu *parse_pxefile(cmd_tbl_t *cmdtp, char *menucfg)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    {
    	struct pxe_menu *cfg;
    
    	cfg = malloc(sizeof(struct pxe_menu));
    
    	if (!cfg)
    		return NULL;
    
    	memset(cfg, 0, sizeof(struct pxe_menu));
    
    	INIT_LIST_HEAD(&cfg->labels);
    
    
    	if (parse_pxefile_top(cmdtp, menucfg, cfg, 1) < 0) {
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		destroy_pxe_menu(cfg);
    		return NULL;
    	}
    
    	return cfg;
    }
    
    /*
     * Converts a pxe_menu struct into a menu struct for use with U-boot's generic
     * menu code.
     */
    static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg)
    {
    	struct pxe_label *label;
    	struct list_head *pos;
    	struct menu *m;
    	int err;
    
    	int i = 1;
    	char *default_num = NULL;
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    	/*
    	 * Create a menu and add items for all the labels.
    	 */
    
    	m = menu_create(cfg->title, cfg->timeout, cfg->prompt, label_print,
    			NULL, NULL);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    	if (!m)
    		return NULL;
    
    	list_for_each(pos, &cfg->labels) {
    		label = list_entry(pos, struct pxe_label, list);
    
    
    		sprintf(label->num, "%d", i++);
    		if (menu_item_add(m, label->num, label) != 1) {
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    			menu_destroy(m);
    			return NULL;
    		}
    
    		if (cfg->default_label &&
    
    		    (strcmp(label->name, cfg->default_label) == 0))
    
    			default_num = label->num;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	}
    
    	/*
    	 * After we've created items for each label in the menu, set the
    	 * menu's default label if one was specified.
    	 */
    
    	if (default_num) {
    		err = menu_default_set(m, default_num);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    		if (err != 1) {
    			if (err != -ENOENT) {
    				menu_destroy(m);
    				return NULL;
    			}
    
    			printf("Missing default: %s\n", cfg->default_label);
    		}
    	}
    
    	return m;
    }
    
    /*
     * Try to boot any labels we have yet to attempt to boot.
     */
    
    static void boot_unattempted_labels(cmd_tbl_t *cmdtp, struct pxe_menu *cfg)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    {
    	struct list_head *pos;
    	struct pxe_label *label;
    
    	list_for_each(pos, &cfg->labels) {
    		label = list_entry(pos, struct pxe_label, list);
    
    		if (!label->attempted)
    
    			label_boot(cmdtp, label);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	}
    }
    
    /*
     * Boot the system as prescribed by a pxe_menu.
     *
     * Use the menu system to either get the user's choice or the default, based
     * on config or user input.  If there is no default or user's choice,
     * attempted to boot labels in the order they were given in pxe files.
     * If the default or user's choice fails to boot, attempt to boot other
     * labels in the order they were given in pxe files.
     *
     * If this function returns, there weren't any labels that successfully
     * booted, or the user interrupted the menu selection via ctrl+c.
     */
    
    static void handle_pxe_menu(cmd_tbl_t *cmdtp, struct pxe_menu *cfg)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    {
    	void *choice;
    	struct menu *m;
    	int err;
    
    	m = pxe_menu_to_menu(cfg);
    	if (!m)
    		return;
    
    	err = menu_get_choice(m, &choice);
    
    	menu_destroy(m);
    
    
    	/*
    	 * err == 1 means we got a choice back from menu_get_choice.
    	 *
    	 * err == -ENOENT if the menu was setup to select the default but no
    	 * default was set. in that case, we should continue trying to boot
    	 * labels that haven't been attempted yet.
    	 *
    	 * otherwise, the user interrupted or there was some other error and
    	 * we give up.
    	 */
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    
    		err = label_boot(cmdtp, choice);
    
    		if (!err)
    			return;
    	} else if (err != -ENOENT) {
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    
    	boot_unattempted_labels(cmdtp, cfg);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    }
    
    /*
     * Boots a system using a pxe file
     *
     * Returns 0 on success, 1 on error.
     */
    static int
    do_pxe_boot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    	unsigned long pxefile_addr_r;
    	struct pxe_menu *cfg;
    	char *pxefile_addr_str;
    
    
    	do_getfile = do_get_tftp;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	if (argc == 1) {
    		pxefile_addr_str = from_env("pxefile_addr_r");
    		if (!pxefile_addr_str)
    			return 1;
    
    	} else if (argc == 2) {
    		pxefile_addr_str = argv[1];
    	} else {
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	}
    
    	if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) {
    		printf("Invalid pxefile address: %s\n", pxefile_addr_str);
    		return 1;
    	}
    
    
    	cfg = parse_pxefile(cmdtp, (char *)(pxefile_addr_r));
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    	if (cfg == NULL) {
    		printf("Error parsing config file\n");
    		return 1;
    	}
    
    
    	handle_pxe_menu(cmdtp, cfg);
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    	destroy_pxe_menu(cfg);
    
    	return 0;
    }
    
    static cmd_tbl_t cmd_pxe_sub[] = {
    	U_BOOT_CMD_MKENT(get, 1, 1, do_pxe_get, "", ""),
    	U_BOOT_CMD_MKENT(boot, 2, 1, do_pxe_boot, "", "")
    };
    
    int do_pxe(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    	cmd_tbl_t *cp;
    
    	if (argc < 2)
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    
    
    	is_pxe = true;
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    	/* drop initial "pxe" arg */
    	argc--;
    	argv++;
    
    	cp = find_cmd_tbl(argv[0], cmd_pxe_sub, ARRAY_SIZE(cmd_pxe_sub));
    
    	if (cp)
    		return cp->cmd(cmdtp, flag, argc, argv);
    
    
    Jason Hobbs's avatar
    Jason Hobbs committed
    }
    
    U_BOOT_CMD(
    	pxe, 3, 1, do_pxe,
    	"commands to get and boot from pxe files",
    	"get - try to retrieve a pxe file using tftp\npxe "
    	"boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n"
    );
    
    
    /*
     * Boots a system using a local disk syslinux/extlinux file
     *
     * Returns 0 on success, 1 on error.
     */
    int do_sysboot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    	unsigned long pxefile_addr_r;
    	struct pxe_menu *cfg;
    	char *pxefile_addr_str;
    	char *filename;
    	int prompt = 0;
    
    
    	is_pxe = false;
    
    
    	if (strstr(argv[1], "-p")) {
    		prompt = 1;
    		argc--;
    		argv++;
    	}
    
    	if (argc < 4)
    		return cmd_usage(cmdtp);
    
    	if (argc < 5) {
    		pxefile_addr_str = from_env("pxefile_addr_r");
    		if (!pxefile_addr_str)
    			return 1;
    	} else {
    		pxefile_addr_str = argv[4];
    	}
    
    	if (argc < 6)
    		filename = getenv("bootfile");
    	else {
    		filename = argv[5];
    		setenv("bootfile", filename);
    	}
    
    	if (strstr(argv[3], "ext2"))
    		do_getfile = do_get_ext2;
    	else if (strstr(argv[3], "fat"))
    		do_getfile = do_get_fat;
    	else {
    		printf("Invalid filesystem: %s\n", argv[3]);
    		return 1;
    	}
    	fs_argv[1] = argv[1];
    	fs_argv[2] = argv[2];
    
    	if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) {
    		printf("Invalid pxefile address: %s\n", pxefile_addr_str);
    		return 1;
    	}
    
    
    	if (get_pxe_file(cmdtp, filename, (void *)pxefile_addr_r) < 0) {
    
    		printf("Error reading config file\n");
    		return 1;
    	}
    
    
    	cfg = parse_pxefile(cmdtp, (char *)(pxefile_addr_r));
    
    
    	if (cfg == NULL) {
    		printf("Error parsing config file\n");
    		return 1;
    	}
    
    	if (prompt)
    		cfg->prompt = 1;
    
    
    	handle_pxe_menu(cmdtp, cfg);
    
    
    	destroy_pxe_menu(cfg);
    
    	return 0;
    }
    
    U_BOOT_CMD(
    	sysboot, 7, 1, do_sysboot,
    	"command to get and boot from syslinux files",
    	"[-p] <interface> <dev[:part]> <ext2|fat> [addr] [filename]\n"
    	"    - load and parse syslinux menu file 'filename' from ext2 or fat\n"
    	"      filesystem on 'dev' on 'interface' to address 'addr'"
    );