464 lines
15 KiB
C
464 lines
15 KiB
C
/* *****************************************************************************
|
|
* p7/args.c -- p7 command-line argument parsing.
|
|
* Copyright (C) 2016-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 <getopt.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
/* ************************************************************************** */
|
|
/* Help and version messages */
|
|
/* ************************************************************************** */
|
|
/* Version message */
|
|
static const char version_message[] =
|
|
QUOTE(BIN) " - from " QUOTE(NAME) " v" QUOTE(VERSION)
|
|
" (licensed under GPLv2)\n"
|
|
"Maintained by " QUOTE(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.";
|
|
|
|
/* Parts */
|
|
#define FOOT \
|
|
"\nType \"" QUOTE(BIN) " --help\" for other subcommands and general options."
|
|
|
|
/* Sending help message */
|
|
static const char help_send[] =
|
|
"Usage: " QUOTE(BIN) " send [-f] [-o <on-calc filename>]\n"
|
|
" [-d <on-calc directory>] [-#] <local file>\n"
|
|
"Send a file to the calculator.\n"
|
|
"\n"
|
|
"Options are:\n"
|
|
" -f, --force Overwrite without asking\n"
|
|
" -o <name> The output filename on the calculator (by default, the same\n"
|
|
" as the local file)\n"
|
|
" -d <dir> The directory on-calc in which the file will be stored (by\n"
|
|
" default, the root directory)\n"
|
|
" -# Display a nice little loading bar\n"
|
|
FOOT;
|
|
|
|
/* Getting help message */
|
|
static const char help_get[] =
|
|
"Usage: " QUOTE(BIN) " get [-o <local file>]\n"
|
|
" [-d <on-calc directory>] <on-calc filename>\n"
|
|
"Request a file from the calculator.\n"
|
|
"\n"
|
|
"Options are:\n"
|
|
" -o <name> The output filename (by default, the same as the on-calc file)\n"
|
|
" -d <dir> The directory on-calc in which to get the file (by default,\n"
|
|
" the root directory)\n"
|
|
" -# Display a nice little loading bar (if output isn't stdout)\n"
|
|
FOOT;
|
|
|
|
/* Copying help message */
|
|
static const char help_copy[] =
|
|
"Usage: " QUOTE(BIN) " copy [-d <source directory]\n"
|
|
" [-t <destination directory>] <source file> <dest file>\n"
|
|
"Copies a file into the other on the calculator.\n"
|
|
"\n"
|
|
"Options are:\n"
|
|
" -d <srcdir> The source directory (by default, the root directory)\n"
|
|
" -t <dstdir> The dest. directory (by default, the root directory)\n"
|
|
FOOT;
|
|
|
|
/* Deleting help message */
|
|
static const char help_del[] =
|
|
"Usage: " QUOTE(BIN) " delete [-d <on-calc directory] <on-calc filename>\n"
|
|
"Delete a file on the calculator.\n"
|
|
"\n"
|
|
"Options are:\n"
|
|
" -d <dir> The directory on-calc in which to remove the file (by default,\n"
|
|
" the root directory)\n"
|
|
FOOT;
|
|
|
|
/* Listing help message */
|
|
static const char help_list[] =
|
|
"Usage: " QUOTE(BIN) " list\n"
|
|
"List files on the distant filesystem.\n"
|
|
FOOT;
|
|
|
|
/* Resetting help message */
|
|
static const char help_reset[] =
|
|
"Usage: " QUOTE(BIN) " reset\n"
|
|
"Reset the distant filesystem.\n"
|
|
FOOT;
|
|
|
|
/* Optimizing help message */
|
|
static const char help_optimize[] =
|
|
"Usage: " QUOTE(BIN) " optimize\n"
|
|
"Optimize the distant filesystem.\n"
|
|
FOOT;
|
|
|
|
/* Dumping help message */
|
|
static const char help_info[] =
|
|
"Usage: " QUOTE(BIN) " info\n"
|
|
"Dump information about the calculator.\n"
|
|
FOOT;
|
|
|
|
/* List serial devices */
|
|
static const char help_listcom[] =
|
|
"Usage: " QUOTE(BIN) " list-devices\n"
|
|
"List serial devices.\n"
|
|
FOOT;
|
|
|
|
/* Idle */
|
|
static const char help_idle[] =
|
|
"Usage: " QUOTE(BIN) " idle\n"
|
|
"Only initialize or end the communication.\n"
|
|
"This subcommand is useful when used with `--no-init` and/or `--no-exit`.\n"
|
|
"It allows you to prepare the communication state for the next calls, or to\n"
|
|
"end a series of calls, without any side effects.\n"
|
|
FOOT;
|
|
/* ************************************************************************** */
|
|
/* Help helpers */
|
|
/* ************************************************************************** */
|
|
/* Main help message parts */
|
|
static const char help_main_part0[] =
|
|
"Usage: " QUOTE(BIN) " [--version|-v] [--help|-h] [--no-init] [--no-exit]\n"
|
|
" [--storage <fls0>] [--com <device>]\n"
|
|
" <subcommand> [options...]\n"
|
|
"\n"
|
|
"Subcommands you can use are:\n"
|
|
" send Send a file to the calculator.\n"
|
|
" get Get a file from the calculator.\n"
|
|
" copy Copy a file into another on the calculator.\n"
|
|
" delete Delete a file on the calculator.\n"
|
|
" reset Reset the flash memory.\n"
|
|
" optimize Optimize the distant filesystem.\n"
|
|
" list List files on the distant filesystem.\n"
|
|
" info Dump info about the calculator.\n"
|
|
" idle Do nothing (only start or end the communication)\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"
|
|
" --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"
|
|
" --storage <abc0> The storage device with which to interact (fls0, crd0).\n"
|
|
" Default storage device is '" QUOTE(DEFAULT_STORAGE) "'.\n"
|
|
" --no-exit Does not terminate connection when action is completed.\n"
|
|
" --no-init Does not initialize connection (should only be used\n"
|
|
" when --no-exit was used last time p7 was called).\n"
|
|
" --use <settings> Use the following serial settings (when used with `--com`).\n"
|
|
" For example, \"9600N2\" represents 9600 bauds, no parity,\n"
|
|
" and two stop bits. (E for even parity, O for odd parity)\n"
|
|
" --set <settings> Set the following serial settings (when used with `--com`).\n"
|
|
" The string has the same format than for `--use`.\n";
|
|
|
|
static const char help_main_loglevel_init[] =
|
|
" --log <level> The library log levels (default: %s).\n"
|
|
" One of: %s";
|
|
|
|
static const char help_main_part1[] =
|
|
" --reset Reset the default communication settings (9600N2).\n"
|
|
"\n"
|
|
"Type \"" QUOTE(BIN) " <subcommand> --help\" for some help about the subcommand.\n"
|
|
"Report bugs to " QUOTE(MAINTAINER) ".\n";
|
|
|
|
/**
|
|
* put_loglevel:
|
|
* Put a loglevel (for listing).
|
|
*
|
|
* @arg initialized if the list was initialized.
|
|
* @arg level the level string.
|
|
*/
|
|
|
|
static void put_loglevel(int *initialized, const char *level)
|
|
{
|
|
/* initialize */
|
|
if (!(*initialized)) {
|
|
printf(help_main_loglevel_init, p7_loglevel_tostring(p7_getlog()),
|
|
level);
|
|
*initialized = 1;
|
|
} else
|
|
printf(", %s", level);
|
|
}
|
|
|
|
/**
|
|
* put_main_help:
|
|
* Put the main help on standard header.
|
|
*/
|
|
|
|
static void put_main_help(void)
|
|
{
|
|
int initialized;
|
|
|
|
/* first big part */
|
|
fputs(help_main_part0, stdout);
|
|
|
|
/* loglevels */
|
|
initialized = 0;
|
|
p7_loglevel_list((p7_liststr_t*)&put_loglevel, (void*)&initialized);
|
|
if (initialized) fputc('\n', stdout);
|
|
|
|
/* second big part */
|
|
fputs(help_main_part1, stdout);
|
|
}
|
|
/* ************************************************************************** */
|
|
/* Main function */
|
|
/* ************************************************************************** */
|
|
/* useful macros */
|
|
#define sub_init(CMD, NARGS) \
|
|
args->menu = mn_##CMD; \
|
|
if (help || aac != 1 + (NARGS)) { \
|
|
puts(help_##CMD); \
|
|
return (0); \
|
|
}
|
|
|
|
/**
|
|
* parse_args:
|
|
* Args parsing main function.
|
|
*
|
|
* Was my very first experiment with getopt.
|
|
* Then I took an arrow in the knee.
|
|
*
|
|
* @arg ac the arguments count
|
|
* @arg av the arguments values
|
|
* @arg args the parsed args pointer
|
|
* @return if it was successfully parsed
|
|
*/
|
|
|
|
int parse_args(int ac, char **av, args_t *args)
|
|
{
|
|
/* initialize args */
|
|
*args = (args_t){
|
|
.menu = 0,
|
|
.nicedisp = 0,
|
|
.dirname = NULL, .filename = NULL,
|
|
.newdir = NULL, .newname = NULL,
|
|
.local = NULL, .force = 0,
|
|
.com = 0,
|
|
.storage = QUOTE(DEFAULT_STORAGE),
|
|
.initflags = P7_ACTIVE | P7_CHECK | P7_TERM};
|
|
|
|
/* define options */
|
|
char short_options[] = "hvfo:d:t:#";
|
|
struct option long_options[] = {
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"version", no_argument, NULL, 'v'},
|
|
{"com", required_argument, NULL, 'c'},
|
|
{"storage", required_argument, NULL, 's'},
|
|
{"force", no_argument, NULL, 'f'},
|
|
{"output", required_argument, NULL, 'o'},
|
|
{"directory", required_argument, NULL, 'd'},
|
|
{"to", required_argument, NULL, 't'},
|
|
{"no-init", no_argument, NULL, 'i'},
|
|
{"no-start", no_argument, NULL, 'i'},
|
|
{"no-exit", no_argument, NULL, 'e'},
|
|
{"no-term", no_argument, NULL, 'e'},
|
|
{"set", required_argument, NULL, 'S'},
|
|
{"reset", no_argument, NULL, 'R'},
|
|
{"use", required_argument, NULL, 'U'},
|
|
{"log", required_argument, NULL, 'L'},
|
|
|
|
/* sentinel */
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
/* get all options */
|
|
int c; opterr = 0;
|
|
int help = 0, rst = 0;
|
|
const char *s_out = NULL, *s_dir = NULL, *s_todir = NULL;
|
|
const char *s_use = NULL, *s_set = NULL, *s_log = NULL;
|
|
while ((c = getopt_long(ac, av, short_options, long_options, NULL)) != -1) {
|
|
switch (c) {
|
|
/* help */
|
|
case 'h': help = 1; break;
|
|
/* version */
|
|
case 'v': puts(version_message); return (0); break;
|
|
/* force */
|
|
case 'f': args->force = 1; break;
|
|
/* nice display */
|
|
case '#': args->nicedisp = 1; break;
|
|
/* logging */
|
|
case 'L': s_log = optarg; break;
|
|
|
|
/* output file (on calc or local) */
|
|
case 'o': s_out = optarg; break;
|
|
/* directory name */
|
|
case 'd': s_dir = optarg; break;
|
|
/* destination directory name */
|
|
case 't': s_todir = optarg; break;
|
|
|
|
/* com port */
|
|
case 'c': args->com = optarg; break;
|
|
/* storage */
|
|
case 's': args->storage = optarg; break;
|
|
/* force no initialization */
|
|
case 'i': args->initflags &= ~P7_CHECK; break;
|
|
/* force no exit */
|
|
case 'e': args->initflags &= ~P7_TERM; break;
|
|
|
|
/* use and set settings */
|
|
case 'U': s_use = optarg; break;
|
|
case 'S': s_set = optarg; break;
|
|
case 'R': rst = 1; break;
|
|
|
|
/* in case of error */
|
|
case '?':
|
|
if (optopt == 'o')
|
|
log("-o, --output: expected an argument\n");
|
|
else if (optopt == 'd')
|
|
log("-d, --directory: expected an argument\n");
|
|
else if (optopt == 't')
|
|
log("-t, --to: expected an argument\n");
|
|
else if (optopt == 'c')
|
|
log("--com: expected an argument\n");
|
|
else if (optopt == 's')
|
|
log("--storage: expected an argument\n");
|
|
else
|
|
break;
|
|
return (0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* get non-option arguments (subcommand and parameters) */
|
|
int aac = ac - optind;
|
|
char **aav = &av[optind];
|
|
|
|
/* get subcommand and things to check */
|
|
char fpmode[2] = " ";
|
|
args->localpath = NULL;
|
|
/* - all subcommands - */
|
|
if (!aac || !strcmp(aav[0], "help")) {
|
|
put_main_help();
|
|
return (0);
|
|
} else if (!strcmp(aav[0], "version")) {
|
|
puts(version_message);
|
|
return (0);
|
|
} else if (!strcmp(aav[0], "list-devices")) {
|
|
if (help || aac > 1) puts(help_listcom);
|
|
else list_devices();
|
|
return (0);
|
|
} else if (!strcmp(aav[0], "info")) {
|
|
sub_init(info, 0)
|
|
} else if (!strcmp(aav[0], "list") || !strcmp(aav[0], "ls")) {
|
|
sub_init(list, 0)
|
|
} else if (!strcmp(aav[0], "reset")) {
|
|
sub_init(reset, 0)
|
|
} else if (!strcmp(aav[0], "optimize")) {
|
|
sub_init(optimize, 0)
|
|
} else if (!strcmp(aav[0], "send")) {
|
|
sub_init(send, 1)
|
|
|
|
/* put arguments to check */
|
|
fpmode[0] = 'r';
|
|
args->localpath = aav[1];
|
|
args->dirname = s_dir ? s_dir : NULL;
|
|
if (s_out) args->filename = s_out;
|
|
else {
|
|
char *rs = strrchr(args->localpath, '/');
|
|
args->filename = rs ? rs + 1 : args->localpath;
|
|
}
|
|
} else if (!strcmp(aav[0], "get")) {
|
|
sub_init(get, 1)
|
|
|
|
/* put arguments to check */
|
|
fpmode[0] = 'w';
|
|
args->filename = aav[1];
|
|
args->dirname = s_dir;
|
|
args->localpath = s_out ? s_out : args->filename;
|
|
} else if (!strcmp(aav[0], "copy")) {
|
|
sub_init(copy, 2)
|
|
|
|
/* get filename */
|
|
args->filename = aav[1];
|
|
args->dirname = s_dir;
|
|
args->newname = aav[2];
|
|
args->newdir = s_todir;
|
|
} else if (!strcmp(aav[0], "del") || !strcmp(aav[0], "delete")) {
|
|
sub_init(del, 1)
|
|
|
|
/* get filename */
|
|
args->filename = aav[1];
|
|
args->dirname = s_dir;
|
|
} else if (!strcmp(aav[0], "idle") || !strcmp(aav[0], "laze")) {
|
|
sub_init(idle, 0)
|
|
} else {
|
|
/* unknown subcommand ! */
|
|
log("Unknown subcommand '%s'.\n", aav[0]);
|
|
return (0);
|
|
}
|
|
|
|
/* check string lengths */
|
|
int noerror = 0;
|
|
if (args->filename && strnlen(args->filename, 13) == 13)
|
|
log("On-calc filename must have 12 chars or less!\n");
|
|
else if (args->newname && strnlen(args->newname, 13) == 13)
|
|
log("Destination filename must have 12 chars or less!\n");
|
|
else if (args->dirname && strnlen(args->dirname, 9) == 9)
|
|
log("On-calc directory name must have 8 chars or less!\n");
|
|
else if (args->newdir && strnlen(args->newdir, 9) == 9)
|
|
log("Destination directory name must have 8 chars or less!\n");
|
|
else if (strnlen(args->storage, 5) != 4)
|
|
log("Storage device (%s) should be 4 chars long!\n", args->storage);
|
|
else
|
|
noerror = 1;
|
|
if (!noerror) return (0);
|
|
|
|
/* use serial settings */
|
|
if (s_use) {
|
|
if (args->com) args->use = &args->_use;
|
|
if (p7_makesettings(&args->_use, s_use)) {
|
|
log("--use: invalid format!\n");
|
|
log("--use: expected <speed><parity><stopbits>, "
|
|
"e.g. 9600N2 or 115200E1!\n");
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* set serial settings */
|
|
if (rst)
|
|
args->do_the_set = 1;
|
|
else if (s_set) {
|
|
args->do_the_set = 1;
|
|
if (args->com) args->set = &args->_set;
|
|
if (p7_makesettings(&args->_set, s_set)) {
|
|
log("--set: invalid format!\n");
|
|
log("--set: expected <speed><parity><stopbits>, "
|
|
"e.g. 9600N2 or 115200E1!\n");
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* check local path */
|
|
if (args->localpath) {
|
|
if (fpmode[0] == 'w' && !strcmp(args->localpath, "-"))
|
|
args->local = stdout;
|
|
else if (!(args->local = fopen(args->localpath, fpmode))) {
|
|
log("Could not open local file : %s\n", strerror(errno));
|
|
if (fpmode[0] == 'w')
|
|
remove(args->localpath);
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* set the log level */
|
|
if (s_log)
|
|
p7_setlog(p7_loglevel_fromstring(s_log));
|
|
|
|
/* everything went well */
|
|
return (1);
|
|
}
|