libcasio/src/p7os/args.c

375 lines
8.9 KiB
C

/* ****************************************************************************
* p7os/args.c -- p7os command-line arguments parsing utility.
* Copyright (C) 2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
*
* This file is part of p7utils.
* p7utils is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2.0 of the License,
* or (at your option) any later version.
*
* p7utils is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with p7utils; if not, see <http://www.gnu.org/licenses/>.
* ************************************************************************* */
#include "main.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <errno.h>
/* ---
* Help and version messages.
* --- */
/* The version message - that's when the President comes in */
static const char version_message[] =
BIN " - from " NAME " v" VERSION " (licensed under GPLv2)\n"
"Maintained by " MAINTAINER ".\n"
"\n"
"This is free software; see the source for copying conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or\n"
"FITNESS FOR A PARTICULAR PURPOSE.";
/* Main help message */
static const char help_main0[] =
"Usage: " BIN " [--version|-v] [--help|-h] [--com <device>]\n"
" [--no-prepare] [--uexe <path>]\n"
" <subcommand> [options...]\n"
"\n"
"This program interacts with a CASIO calculator's firmware.\n"
"Keep in mind that using it is HIGHLY DANGEROUS and could easily brick your\n"
"calculator if you aren't careful enough. AVOID USING IT IF YOU DO NOT\n"
"KNOW WHAT YOU'RE DOING.\n"
"\n"
"Subcommands you can use are :\n"
" prepare-only Set-up the update program, but leave it for other "
"programs\n"
" to interact with it.\n"
" get Get the OS image.\n"
" flash Flash the OS image.\n"
"\n"
"General options:\n"
" -h, --help Display the help page of the (sub)command and quit.\n"
" -v, --version Display the version message and quit.\n";
static const char help_log[] =
" --log <level> The library log level (default: %s).\n"
" One of: %s";
static const char help_main1[] =
" --com <device> The serial device, if you want to communicate with a\n"
" calculator connected using a USB-to-serial cable.\n"
" If this option isn't used, the program will look for a\n"
" calculator connected using direct USB.\n"
" --no-prepare Use the current environment, instead of uploading one.\n"
" --erase-flash Instead of 0xA0270000 the last erase addr is 0xA0400000.\n"
" -u, --uexe <path> Use a custom update program.\n"
" If `--no-prepare` is not given, this option is "
"required.\n"
"\n"
"Type \"" BIN " <subcommand> --help\" for some help about a subcommand.\n"
"Report bugs to " MAINTAINER ".";
/* Subcommands help messages footer. */
#define FOOT \
"\nType \"" BIN " --help\" for other subcommands and general options."
/* Help message for prepare subcommand */
static const char help_prepare_only[] =
"Usage: " BIN " prepare-only\n"
"Send the P7 server on the calculator for further operations.\n"
"This must be used before any other p7os operation.\n"
FOOT;
/* Help message for get subcommand */
static const char help_get[] =
"Usage: " BIN " get [-o <os.bin>]\n"
"Get the calculator OS image.\n"
"\n"
"Options are :\n"
" -o <os.bin> Where to store the image (default is \"os.bin\")\n"
FOOT;
/* Help message for flash subcommand. */
static const char help_flash[] =
"Usage: " BIN " flash <rom.bin>\n"
"Flash the calculator's OS image.\n"
FOOT;
/* ---
* Put the help message.
* --- */
/**
* put_help:
* Put the main help message.
*/
static void put_help(void)
{
/* first big part */
fputs(help_main0, stdout);
/* Loglevels. */
{
casio_iter_t *iter;
char *first = NULL, *current;
int pos = 0;
if (!casio_iter_log(&iter)) {
while (!casio_next_log(iter, &current)) {
if (!first) {
size_t len = strlen(current) + 1;
first = malloc(len);
if (!first)
break ;
memcpy(first, current, len);
pos++;
continue ;
}
if (pos == 1)
printf(help_log, casio_getlog(), first);
printf(", %s", current);
pos++;
}
if (pos > 1)
fputc('\n', stdout);
free(first);
casio_end(iter);
}
}
/* second big part */
puts(help_main1);
}
/* ---
* Main argument parsing function.
* --- */
/* Help macro. */
#define sub_init(CMD, NARGS) { \
args->menu = mn_##CMD; \
if (help || pc != (NARGS)) { \
puts(help_##CMD); \
return (1); \
}}
/* Options. */
static const char shopts[] = "hvu:o:#";
static const struct option longopts[] = {
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{"com", required_argument, NULL, 'c'},
{"no-prepare", no_argument, NULL, 'n'},
{"erase-flash", no_argument, NULL, 'e'},
{"uexe", required_argument, NULL, 'u'},
{"output", required_argument, NULL, 'o'},
{"log", required_argument, NULL, 'l'},
{NULL, 0, NULL, 0}
};
/**
* parse_args:
* Args parsing main function.
*
* Based on my very first experiment with getopt.
*
* @arg ac the arguments count
* @arg av the arguments values
* @arg args the parsed args pointer
* @return if has been parsed successfully
*/
int parse_args(int ac, char **av, args_t *args)
{
int c, help = 0, version = 0, pc;
char **pv, *sub;
const char *s_out = "os.bin", *s_uexe = NULL, *s_log = NULL;
/* Initialize the arguments. */
args->menu = 0;
args->noprepare = 0;
args->com = NULL;
args->local = NULL;
args->localpath = NULL;
args->uexe = NULL;
/* Get all options. */
opterr = 0;
while ((c = getopt_long(ac, av, shopts, longopts, NULL)) >= 0) switch (c) {
case 'h':
help = 1;
break;
case 'v':
version = 1;
break;
/* COM port, should prepare or not, should erase flash or not. */
case 'c':
args->com = optarg;
break;
case 'n':
args->noprepare = 1;
break;
case 'e':
args->eraseflash = 1;
break;
/* log level, Update.Exe, output path */
case 'l':
s_log = optarg;
break;
case 'u':
s_uexe = optarg;
break;
case 'o':
s_out = optarg;
break;
/* Error. */
case '?':
if (optopt == 'o')
fprintf(stderr, "-o, --output: expected an argument\n");
else if (optopt == 'c')
fprintf(stderr, "--com: expected an argument\n");
else if (optopt == 'u')
fprintf(stderr, "-u, --uexe: expected an argument\n");
else
break;
return (1);
}
/* Check for version. */
if (version) {
puts(version_message);
return (1);
}
/* Get non-option arguments (subcommand and parameters). */
pc = ac - optind;
pv = &av[optind];
sub = pc ? pv[0] : NULL;
pc--;
pv++;
/* Subcommand parsing. */
char fpmode[3] = "r\0";
if (!sub || !strcmp(sub, "help")) {
put_help();
return (1);
} else if (!strcmp(sub, "version")) {
puts(version_message);
return (1);
} else if (!strcmp(sub, "prepare-only")) {
sub_init(prepare_only, 0)
if (args->noprepare) {
fprintf(stderr,
"So we should prepare but we should not prepare? Duh!\n");
return (1);
}
} else if (!strcmp(sub, "get")) {
sub_init(get, 0)
args->localpath = s_out;
fpmode[0] = 'w';
} else if (!strcmp(sub, "flash")) {
sub_init(flash, 1)
args->localpath = pv[0];
} else {
fprintf(stderr, "Unknown subcommand '%s'.\n", sub);
return (1);
}
/* Open destination file. */
if (args->localpath) {
FILE *localfile = fopen(args->localpath, fpmode);
if (!localfile) {
fprintf(stderr, "Could not open local file: %s\n",
strerror(errno));
return (1);
}
int err = casio_open_stream_file(&args->local,
fpmode[0] == 'r' ? localfile : NULL,
fpmode[0] == 'w' || fpmode[1] == '+' ? localfile : NULL,
1, 1);
if (err) {
fprintf(stderr, "Could not make a stream out of local file: %s\n",
casio_strerror(err));
return (1);
}
}
/* Open update.exe file. */
if (!args->noprepare) {
FILE *uexe;
int err;
if (!s_uexe) {
fprintf(stderr,
"One of `-u <file>` or `--no-prepare` is expected!\n");
if (args->local)
casio_close(args->local);
return (1);
}
uexe = fopen(s_uexe, "r");
if (!uexe) {
fprintf(stderr, "Could not open update program: %s\n",
strerror(errno));
if (args->local)
casio_close(args->local);
return (1);
}
err = casio_open_stream_file(&args->uexe,
uexe, NULL, 1, 0);
if (err) {
fprintf(stderr,
"Could not make a stream out of the update.exe: %s\n",
casio_strerror(err));
if (args->local)
casio_close(args->local);
return (1);
}
}
/* Set the log level. */
if (s_log)
casio_setlog(s_log);
return (0);
}