/* * RouterBOOT configuration utility * * Copyright (C) 2010 Gabor Juhos * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * */ #include #include #include #include #include #include #include #include #include #include "rbcfg.h" #include "cyg_crc.h" #define RBCFG_TMP_FILE "/tmp/.rbcfg" #define RBCFG_MTD_NAME "soft_config" #define RB_ERR_NOTFOUND 1 #define RB_ERR_INVALID 2 #define RB_ERR_NOMEM 3 #define RB_ERR_IO 4 #define ARRAY_SIZE(_a) (sizeof((_a)) / sizeof((_a)[0])) struct rbcfg_ctx { char *mtd_device; char *tmp_file; char *buf; unsigned buflen; }; struct rbcfg_value { const char *name; const char *desc; union { uint32_t u32; const char *raw; } val; }; #define RBCFG_ENV_TYPE_U32 0 struct rbcfg_env { const char *name; int type; uint16_t id; const struct rbcfg_value *values; int num_values; }; #define CMD_FLAG_USES_CFG 0x01 struct rbcfg_command { const char *command; const char *usage; int flags; int (*exec)(int argc, const char *argv[]); }; static void usage(void); /* Globals */ static struct rbcfg_ctx *rbcfg_ctx; static char *rbcfg_name; #define CFG_U32(_name, _desc, _val) { \ .name = (_name), \ .desc = (_desc), \ .val.u32 = (_val), \ } static const struct rbcfg_value rbcfg_boot_delay[] = { CFG_U32("1", "1 second", RB_BOOT_DELAY_1SEC), CFG_U32("2", "2 seconds", RB_BOOT_DELAY_2SEC), CFG_U32("3", "3 seconds", RB_BOOT_DELAY_3SEC), CFG_U32("4", "4 seconds", RB_BOOT_DELAY_4SEC), CFG_U32("5", "5 seconds", RB_BOOT_DELAY_5SEC), CFG_U32("6", "6 seconds", RB_BOOT_DELAY_6SEC), CFG_U32("7", "7 seconds", RB_BOOT_DELAY_7SEC), CFG_U32("8", "8 seconds", RB_BOOT_DELAY_8SEC), CFG_U32("9", "9 seconds", RB_BOOT_DELAY_9SEC), }; static const struct rbcfg_value rbcfg_boot_device[] = { CFG_U32("eth", "boot over Ethernet", RB_BOOT_DEVICE_ETHER), CFG_U32("nandeth", "boot from NAND, if fail then Ethernet", RB_BOOT_DEVICE_NANDETH), CFG_U32("ethnand", "boot Ethernet once, then NAND", RB_BOOT_DEVICE_ETHONCE), CFG_U32("nand", "boot from NAND only", RB_BOOT_DEVICE_NANDONLY), }; static const struct rbcfg_value rbcfg_boot_key[] = { CFG_U32("any", "any key", RB_BOOT_KEY_ANY), CFG_U32("del", " key only", RB_BOOT_KEY_DEL), }; static const struct rbcfg_value rbcfg_boot_protocol[] = { CFG_U32("bootp", "BOOTP protocol", RB_BOOT_PROTOCOL_BOOTP), CFG_U32("dhcp", "DHCP protocol", RB_BOOT_PROTOCOL_DHCP), }; static const struct rbcfg_value rbcfg_uart_speed[] = { CFG_U32("115200", "", RB_UART_SPEED_115200), CFG_U32("57600", "", RB_UART_SPEED_57600), CFG_U32("38400", "", RB_UART_SPEED_38400), CFG_U32("19200", "", RB_UART_SPEED_19200), CFG_U32("9600", "", RB_UART_SPEED_9600), CFG_U32("4800", "", RB_UART_SPEED_4800), CFG_U32("2400", "", RB_UART_SPEED_2400), CFG_U32("1200", "", RB_UART_SPEED_1200), CFG_U32("off", "disable console output", RB_UART_SPEED_OFF), }; static const struct rbcfg_value rbcfg_cpu_mode[] = { CFG_U32("powersave", "power save", RB_CPU_MODE_POWERSAVE), CFG_U32("regular", "regular (better for -0c environment)", RB_CPU_MODE_REGULAR), }; static const struct rbcfg_value rbcfg_booter[] = { CFG_U32("regular", "load regular booter", RB_BOOTER_REGULAR), CFG_U32("backup", "force backup-booter loading", RB_BOOTER_BACKUP), }; static const struct rbcfg_env rbcfg_envs[] = { { .name = "boot_delay", .id = RB_ID_BOOT_DELAY, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_boot_delay, .num_values = ARRAY_SIZE(rbcfg_boot_delay), }, { .name = "boot_device", .id = RB_ID_BOOT_DEVICE, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_boot_device, .num_values = ARRAY_SIZE(rbcfg_boot_device), }, { .name = "boot_key", .id = RB_ID_BOOT_KEY, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_boot_key, .num_values = ARRAY_SIZE(rbcfg_boot_key), }, { .name = "boot_protocol", .id = RB_ID_BOOT_PROTOCOL, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_boot_protocol, .num_values = ARRAY_SIZE(rbcfg_boot_protocol), }, { .name = "booter", .id = RB_ID_BOOTER, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_booter, .num_values = ARRAY_SIZE(rbcfg_booter), }, { .name = "cpu_mode", .id = RB_ID_CPU_MODE, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_cpu_mode, .num_values = ARRAY_SIZE(rbcfg_cpu_mode), }, { .name = "uart_speed", .id = RB_ID_UART_SPEED, .type = RBCFG_ENV_TYPE_U32, .values = rbcfg_uart_speed, .num_values = ARRAY_SIZE(rbcfg_uart_speed), } }; static inline uint16_t get_u16(const void *buf) { const uint8_t *p = buf; return ((uint16_t) p[1] + ((uint16_t) p[0] << 8)); } static inline uint32_t get_u32(const void *buf) { const uint8_t *p = buf; return ((uint32_t) p[3] + ((uint32_t) p[2] << 8) + ((uint32_t) p[1] << 16) + ((uint32_t) p[0] << 24)); } static inline void put_u32(void *buf, uint32_t val) { uint8_t *p = buf; p[3] = val & 0xff; p[2] = (val >> 8) & 0xff; p[1] = (val >> 16) & 0xff; p[0] = (val >> 24) & 0xff; } static int rbcfg_find_tag(struct rbcfg_ctx *ctx, uint16_t tag_id, uint16_t *tag_len, void **tag_data) { uint16_t id; uint16_t len; char *buf = ctx->buf; unsigned int buflen = ctx->buflen; int ret = RB_ERR_NOTFOUND; /* skip magic and CRC value */ buf += 8; buflen -= 8; while (buflen > 2) { len = get_u16(buf); buf += 2; buflen -= 2; if (buflen < 2) break; id = get_u16(buf); buf += 2; buflen -= 2; if (id == RB_ID_TERMINATOR) break; if (buflen < len) break; if (id == tag_id) { *tag_len = len; *tag_data = buf; ret = 0; break; } buf += len; buflen -= len; } if (ret) fprintf(stderr, "no tag found with id=%u\n", tag_id); return ret; } static int rbcfg_get_u32(struct rbcfg_ctx *ctx, uint16_t id, uint32_t *val) { void *tag_data; uint16_t tag_len; int err; err = rbcfg_find_tag(ctx, id, &tag_len, &tag_data); if (err) return err; *val = get_u32(tag_data); return 0; } static int rbcfg_set_u32(struct rbcfg_ctx *ctx, uint16_t id, uint32_t val) { void *tag_data; uint16_t tag_len; int err; err = rbcfg_find_tag(ctx, id, &tag_len, &tag_data); if (err) return err; put_u32(tag_data, val); return 0; } char *rbcfg_find_mtd(const char *name, int *erase_size) { FILE *f; int mtd_num; char dev[PATH_MAX]; char *ret = NULL; struct stat s; int err; f = fopen("/proc/mtd", "r"); if (!f) return NULL; while (1) { char *p; p = fgets(dev, sizeof(dev), f); if (!p) break; if (!strstr(dev, name)) continue; err = sscanf(dev, "mtd%d: %08x", &mtd_num, erase_size); if (err != 2) break; sprintf(dev, "/dev/mtdblock%d", mtd_num); err = stat(dev, &s); if (err < 0) break; if ((s.st_mode & S_IFBLK) == 0) break; ret = malloc(strlen(dev) + 1); if (ret == NULL) break; strncpy(ret, dev, strlen(dev) + 1); break; } fclose(f); return ret; } static int rbcfg_check_tmp(struct rbcfg_ctx *ctx) { struct stat s; int err; err = stat(ctx->tmp_file, &s); if (err < 0) return 0; if ((s.st_mode & S_IFREG) == 0) return 0; if (s.st_size != ctx->buflen) return 0; return 1; } static int rbcfg_load(struct rbcfg_ctx *ctx) { uint32_t magic; uint32_t crc_orig, crc; char *name; int tmp; int fd; int err; tmp = rbcfg_check_tmp(ctx); name = (tmp) ? ctx->tmp_file : ctx->mtd_device; fd = open(name, O_RDONLY); if (fd < 0) { fprintf(stderr, "unable to open %s\n", name); err = RB_ERR_IO; goto err; } err = read(fd, ctx->buf, ctx->buflen); if (err != ctx->buflen) { fprintf(stderr, "unable to read from %s\n", name); err = RB_ERR_IO; goto err_close; } magic = get_u32(ctx->buf); if (magic != RB_MAGIC_SOFT) { fprintf(stderr, "invalid configuration\n"); err = RB_ERR_INVALID; goto err_close; } crc_orig = get_u32(ctx->buf + 4); put_u32(ctx->buf + 4, 0); crc = cyg_ether_crc32((unsigned char *) ctx->buf, ctx->buflen); if (crc != crc_orig) { fprintf(stderr, "configuration has CRC error\n"); err = RB_ERR_INVALID; goto err_close; } err = 0; err_close: close(fd); err: return err; } static int rbcfg_open() { char *mtd_device; struct rbcfg_ctx *ctx; int buflen; int err; mtd_device = rbcfg_find_mtd(RBCFG_MTD_NAME, &buflen); if (!mtd_device) { fprintf(stderr, "unable to find configuration\n"); return RB_ERR_NOTFOUND; } ctx = malloc(sizeof(struct rbcfg_ctx) + buflen); if (ctx == NULL) { err = RB_ERR_NOMEM; goto err_free_mtd; } ctx->mtd_device = mtd_device; ctx->tmp_file = RBCFG_TMP_FILE; ctx->buflen = buflen; ctx->buf = (char *) &ctx[1]; err = rbcfg_load(ctx); if (err) goto err_free_ctx; rbcfg_ctx = ctx; return 0; err_free_ctx: free(ctx); err_free_mtd: free(mtd_device); return err; } static int rbcfg_update(int tmp) { struct rbcfg_ctx *ctx = rbcfg_ctx; char *name; uint32_t crc; int fd; int err; put_u32(ctx->buf, RB_MAGIC_SOFT); put_u32(ctx->buf + 4, 0); crc = cyg_ether_crc32((unsigned char *) ctx->buf, ctx->buflen); put_u32(ctx->buf + 4, crc); name = (tmp) ? ctx->tmp_file : ctx->mtd_device; fd = open(name, O_WRONLY | O_CREAT); if (fd < 0) { fprintf(stderr, "unable to open %s for writing\n", name); err = RB_ERR_IO; goto out; } err = write(fd, ctx->buf, ctx->buflen); if (err != ctx->buflen) { err = RB_ERR_IO; goto out_close; } fsync(fd); err = 0; out_close: close(fd); out: return err; } static void rbcfg_close(void) { struct rbcfg_ctx *ctx; ctx = rbcfg_ctx; free(ctx->mtd_device); free(ctx); } static const struct rbcfg_value * rbcfg_env_find(const struct rbcfg_env *env, const char *name) { unsigned i; for (i = 0; i < env->num_values; i++) { const struct rbcfg_value *v = &env->values[i]; if (strcmp(v->name, name) == 0) return v; } return NULL; } static const struct rbcfg_value * rbcfg_env_find_u32(const struct rbcfg_env *env, uint32_t val) { unsigned i; for (i = 0; i < env->num_values; i++) { const struct rbcfg_value *v = &env->values[i]; if (v->val.u32 == val) return v; } return NULL; } static const char * rbcfg_env_get_u32(const struct rbcfg_env *env) { const struct rbcfg_value *v; uint32_t val; int err; err = rbcfg_get_u32(rbcfg_ctx, env->id, &val); if (err) return NULL; v = rbcfg_env_find_u32(env, val); if (v == NULL) { fprintf(stderr, "unknown value %08x found for %s\n", val, env->name); return NULL; } return v->name; } static int rbcfg_env_set_u32(const struct rbcfg_env *env, const char *data) { const struct rbcfg_value *v; int err; v = rbcfg_env_find(env, data); if (v == NULL) { fprintf(stderr, "invalid value '%s'\n", data); return RB_ERR_INVALID; } err = rbcfg_set_u32(rbcfg_ctx, env->id, v->val.u32); return err; } static const char * rbcfg_env_get(const struct rbcfg_env *env) { const char *ret = NULL; switch (env->type) { case RBCFG_ENV_TYPE_U32: ret = rbcfg_env_get_u32(env); break; } return ret; } static int rbcfg_env_set(const struct rbcfg_env *env, const char *data) { int ret = 0; switch (env->type) { case RBCFG_ENV_TYPE_U32: ret = rbcfg_env_set_u32(env, data); break; } return ret; } static int rbcfg_cmd_apply(int argc, const char *argv[]) { return rbcfg_update(0); } static int rbcfg_cmd_help(int argc, const char *argv[]) { usage(); return 0; } static int rbcfg_cmd_get(int argc, const char *argv[]) { int err = RB_ERR_NOTFOUND; int i; if (argc != 1) { usage(); return RB_ERR_INVALID; } for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) { const struct rbcfg_env *env = &rbcfg_envs[i]; const char *value; if (strcmp(env->name, argv[0])) continue; value = rbcfg_env_get(env); if (value) { fprintf(stdout, "%s\n", value); err = 0; } break; } return err; } static int rbcfg_cmd_set(int argc, const char *argv[]) { int err = RB_ERR_INVALID; int i; if (argc != 2) { /* not enough parameters */ usage(); return RB_ERR_INVALID; } for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) { const struct rbcfg_env *env = &rbcfg_envs[i]; if (strcmp(env->name, argv[0])) continue; err = rbcfg_env_set(env, argv[1]); if (err == 0) err = rbcfg_update(1); break; } return err; } static int rbcfg_cmd_show(int argc, const char *argv[]) { int i; if (argc != 0) { usage(); return RB_ERR_INVALID; } for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) { const struct rbcfg_env *env = &rbcfg_envs[i]; const char *value; value = rbcfg_env_get(env); if (value) fprintf(stdout, "%s=%s\n", env->name, value); } return 0; } static const struct rbcfg_command rbcfg_commands[] = { { .command = "apply", .usage = "apply\n" "\t- write configuration to the mtd device", .flags = CMD_FLAG_USES_CFG, .exec = rbcfg_cmd_apply, }, { .command = "help", .usage = "help\n" "\t- show this screen", .exec = rbcfg_cmd_help, }, { .command = "get", .usage = "get \n" "\t- get value of the configuration option ", .flags = CMD_FLAG_USES_CFG, .exec = rbcfg_cmd_get, }, { .command = "set", .usage = "set \n" "\t- set value of the configuration option to ", .flags = CMD_FLAG_USES_CFG, .exec = rbcfg_cmd_set, }, { .command = "show", .usage = "show\n" "\t- show value of all configuration options", .flags = CMD_FLAG_USES_CFG, .exec = rbcfg_cmd_show, } }; static void usage(void) { char buf[255]; int len; int i; fprintf(stderr, "Usage: %s \n", rbcfg_name); fprintf(stderr, "\nCommands:\n"); for (i = 0; i < ARRAY_SIZE(rbcfg_commands); i++) { const struct rbcfg_command *cmd; cmd = &rbcfg_commands[i]; len = snprintf(buf, sizeof(buf), "%s", cmd->usage); buf[len] = '\0'; fprintf(stderr, "%s\n", buf); } fprintf(stderr, "\nConfiguration options:\n"); for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) { const struct rbcfg_env *env; int j; env = &rbcfg_envs[i]; fprintf(stderr, "\n%s:\n", env->name); for (j = 0; j < env->num_values; j++) { const struct rbcfg_value *v = &env->values[j]; fprintf(stderr, "\t%-12s %s\n", v->name, v->desc); } } fprintf(stderr, "\n"); } int main(int argc, const char *argv[]) { const struct rbcfg_command *cmd = NULL; int ret; int i; rbcfg_name = (char *) argv[0]; if (argc < 2) { usage(); return EXIT_FAILURE; } for (i = 0; i < ARRAY_SIZE(rbcfg_commands); i++) { if (strcmp(rbcfg_commands[i].command, argv[1]) == 0) { cmd = &rbcfg_commands[i]; break; } } if (cmd == NULL) { fprintf(stderr, "unknown command '%s'\n", argv[1]); usage(); return EXIT_FAILURE; } argc -= 2; argv += 2; if (cmd->flags & CMD_FLAG_USES_CFG) { ret = rbcfg_open(); if (ret) return EXIT_FAILURE; } ret = cmd->exec(argc, argv); if (cmd->flags & CMD_FLAG_USES_CFG) rbcfg_close(); if (ret) return EXIT_FAILURE; return EXIT_SUCCESS; }