2010-07-13 12:15:46 +00:00
|
|
|
/*
|
|
|
|
Serval Distributed Numbering Architecture (DNA)
|
|
|
|
Copyright (C) 2010 Paul Gardner-Stephen
|
|
|
|
|
|
|
|
This program 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
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program 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 this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2012-02-23 02:15:42 +00:00
|
|
|
#include "serval.h"
|
2011-12-18 21:34:31 +00:00
|
|
|
#include "rhizome.h"
|
2011-05-06 02:27:33 +00:00
|
|
|
#include <stdarg.h>
|
2011-10-02 09:58:08 +00:00
|
|
|
#include <signal.h>
|
|
|
|
#include <unistd.h>
|
2012-03-29 04:33:17 +00:00
|
|
|
#include <dirent.h>
|
2011-05-06 02:27:33 +00:00
|
|
|
|
2011-08-10 13:38:59 +00:00
|
|
|
char *gatewayspec=NULL;
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
char *outputtemplate=NULL;
|
2011-05-05 09:10:38 +00:00
|
|
|
char *instrumentation_file=NULL;
|
2011-05-06 02:27:33 +00:00
|
|
|
char *importFile=NULL;
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
int debug=0;
|
|
|
|
int timeout=3000; /* 3000ms request timeout */
|
|
|
|
|
|
|
|
int serverMode=0;
|
|
|
|
int clientMode=0;
|
|
|
|
|
2011-12-04 07:18:51 +00:00
|
|
|
int returnMultiVars=0;
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
int hexdigit[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
|
|
|
|
|
2011-10-02 10:48:16 +00:00
|
|
|
int sock=-1;
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
#ifndef HAVE_BZERO
|
|
|
|
/* OpenWRT doesn't have bzero */
|
|
|
|
void bzero(void *m,size_t len)
|
|
|
|
{
|
|
|
|
unsigned char *c=m;
|
|
|
|
int i;
|
|
|
|
for(i=0;i<len;i++) c[i]=0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int dump(char *name,unsigned char *addr,int len)
|
|
|
|
{
|
|
|
|
int i,j;
|
|
|
|
fprintf(stderr,"Dump of %s\n",name);
|
|
|
|
for(i=0;i<len;i+=16)
|
|
|
|
{
|
|
|
|
fprintf(stderr," %04x :",i);
|
|
|
|
for(j=0;j<16&&(i+j)<len;j++) fprintf(stderr," %02x",addr[i+j]);
|
|
|
|
for(;j<16;j++) fprintf(stderr," ");
|
|
|
|
fprintf(stderr," ");
|
|
|
|
for(j=0;j<16&&(i+j)<len;j++) fprintf(stderr,"%c",addr[i+j]>=' '&&addr[i+j]<0x7f?addr[i+j]:'.');
|
|
|
|
fprintf(stderr,"\n");
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dumpResponses(struct response_set *responses)
|
|
|
|
{
|
|
|
|
struct response *r;
|
|
|
|
if (!responses) {fprintf(stderr,"Response set is NULL\n"); return 0; }
|
|
|
|
fprintf(stderr,"Response set claims to contain %d entries.\n",responses->response_count);
|
|
|
|
r=responses->responses;
|
|
|
|
while(r)
|
|
|
|
{
|
|
|
|
fprintf(stderr," response code 0x%02x\n",r->code);
|
|
|
|
if (r->next)
|
|
|
|
if (r->next->prev!=r) fprintf(stderr," !! response chain is broken\n");
|
|
|
|
r=r->next;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-06 02:27:33 +00:00
|
|
|
int setReason(char *fmt, ...)
|
2010-07-13 12:15:46 +00:00
|
|
|
{
|
2011-05-06 02:27:33 +00:00
|
|
|
va_list ap,ap2;
|
|
|
|
char msg[8192];
|
|
|
|
|
|
|
|
va_start(ap,fmt);
|
|
|
|
va_copy(ap2,ap);
|
|
|
|
|
|
|
|
vsnprintf(msg,8192,fmt,ap2); msg[8191]=0;
|
|
|
|
|
|
|
|
va_end(ap);
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr,"Error: %s\n",msg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-05-06 02:27:33 +00:00
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
int hexvalue(unsigned char c)
|
|
|
|
{
|
|
|
|
if (c>='0'&&c<='9') return c-'0';
|
|
|
|
if (c>='A'&&c<='F') return c-'A'+10;
|
|
|
|
if (c>='a'&&c<='f') return c-'a'+10;
|
|
|
|
return setReason("Invalid hex digit in SID");
|
|
|
|
}
|
|
|
|
|
|
|
|
int parseAssignment(unsigned char *text,int *var_id,unsigned char *value,int *value_len)
|
|
|
|
{
|
|
|
|
/* Parse an assignment.
|
|
|
|
|
|
|
|
Valid formats are:
|
|
|
|
|
|
|
|
var=@file - value comes from named file.
|
|
|
|
var=[[$]value] - value comes from string, and may be empty. $ means value is in hex
|
|
|
|
|
|
|
|
Values are length limited to 65535 bytes.
|
|
|
|
*/
|
|
|
|
|
2012-04-12 23:55:03 +00:00
|
|
|
int i;
|
2010-07-13 12:15:46 +00:00
|
|
|
int max_len=*value_len;
|
|
|
|
int vlen=0;
|
|
|
|
int tlen=strlen((char *)text);
|
|
|
|
|
|
|
|
if (tlen>3072) {
|
|
|
|
return setReason("Variable assignment string is too long, use =@file to read value from a file");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Identify which variable */
|
2012-04-12 23:55:03 +00:00
|
|
|
*var_id=-1;
|
2010-07-13 12:15:46 +00:00
|
|
|
for(i=0;i<tlen;i++) if (text[i]=='=') break;
|
2012-04-12 23:55:03 +00:00
|
|
|
|
|
|
|
/* Go through known keyring variables */
|
|
|
|
if (!strcasecmp((char *)text,"did")) *var_id=KEYTYPE_DID;
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2012-04-12 23:55:03 +00:00
|
|
|
if (*var_id==-1) return setReason("Illegal variable name in assignment");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
i++;
|
|
|
|
switch(text[i])
|
|
|
|
{
|
|
|
|
case '$': /* hex */
|
|
|
|
i++;
|
|
|
|
while(i<tlen) {
|
|
|
|
int b=hexvalue(text[i++])<<4;
|
|
|
|
if (i>=tlen) return setReason("Variable value has an odd number of hex digits.");
|
|
|
|
b|=hexvalue(text[i++]);
|
|
|
|
if (b<0) return setReason("That doesn't look like hex to me");
|
|
|
|
if (vlen>=max_len) return setReason("Variable hex value too long");
|
|
|
|
value[vlen++]=b;
|
|
|
|
}
|
|
|
|
*value_len=vlen;
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case '@': /* file */
|
|
|
|
{
|
|
|
|
FILE *f=fopen((char *)&text[i+1],"r");
|
|
|
|
int flen;
|
|
|
|
fseek(f,0,SEEK_END);
|
|
|
|
flen=ftell(f);
|
|
|
|
if (flen>max_len) return setReason("Variable value from file too long");
|
|
|
|
fseek(f,0,SEEK_SET);
|
|
|
|
vlen=fread(value,1,flen,f);
|
|
|
|
if (vlen!=flen) return setReason("Could not read all of file");
|
|
|
|
fclose(f);
|
|
|
|
*value_len=vlen;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: /* literal string */
|
|
|
|
vlen=strlen((char *)&text[i]);
|
|
|
|
if (vlen>max_len) return setReason("Variable value too long");
|
|
|
|
bcopy(&text[i],value,vlen);
|
|
|
|
*value_len=vlen;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int usage(char *complaint)
|
|
|
|
{
|
|
|
|
fprintf(stderr,"dna: %s\n",complaint);
|
|
|
|
fprintf(stderr,"usage:\n");
|
2012-04-12 23:55:03 +00:00
|
|
|
fprintf(stderr," dna [-v <flags>] -S [-f keyring file] [-N interface,...] [-G gateway specification] [-r rhizome path]\n");
|
2011-08-09 06:23:42 +00:00
|
|
|
fprintf(stderr,"or\n");
|
2011-12-18 19:16:34 +00:00
|
|
|
fprintf(stderr," dna -r <rhizome path> -M <manifest name>\n");
|
|
|
|
fprintf(stderr,"or\n");
|
2011-03-22 06:01:02 +00:00
|
|
|
fprintf(stderr," dna <-d|-s> id -A\n");
|
|
|
|
fprintf(stderr,"or\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr," dna <-d|-s> id [-p pin] [-i variable instance] <-R variable[=value]>\n");
|
2012-01-10 05:26:40 +00:00
|
|
|
fprintf(stderr," [-v <flags>] [-t request timeout in ms] [-O output file name template]\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr,"or\n");
|
|
|
|
fprintf(stderr," dna <-d|-s> id [-p pin] [-i variable instance] <-W|-U|-D variable[=[$|@]value]>\n");
|
2012-01-10 05:26:40 +00:00
|
|
|
fprintf(stderr," [-v <flags>] [-t request timeout in ms]\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr,"or\n");
|
2012-01-10 05:26:40 +00:00
|
|
|
fprintf(stderr," dna [-v <flags>] [-t timeout] -d did -C\n");
|
2011-05-06 02:27:33 +00:00
|
|
|
fprintf(stderr,"or\n");
|
2012-04-12 23:55:03 +00:00
|
|
|
fprintf(stderr," dna [-v <flags>] -f <keyring file> -E <export.txt>\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
|
|
|
fprintf(stderr,"\n");
|
2012-01-10 05:26:40 +00:00
|
|
|
fprintf(stderr," -v - Set verbosity.\n");
|
2011-03-22 06:01:02 +00:00
|
|
|
fprintf(stderr," -A - Ask for address of subscriber.\n");
|
2011-03-21 02:38:35 +00:00
|
|
|
fprintf(stderr," -b - Specify BATMAN socket to obtain peer list (flaky).\n");
|
|
|
|
fprintf(stderr," -l - Specify BATMAN socket to obtain peer list (better, but requires Serval patched BATMAN).\n");
|
2011-05-05 09:10:38 +00:00
|
|
|
fprintf(stderr," -L - Log mesh statistics to specified file.\n");
|
2011-12-17 01:41:32 +00:00
|
|
|
fprintf(stderr," -m - Return multiple variable values instead of only first response.\n");
|
2011-12-18 19:16:34 +00:00
|
|
|
fprintf(stderr," -M - Create and import a new bundle from the specified manifest.\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr," -n - Do not detach from foreground in server mode.\n");
|
2012-04-12 23:55:03 +00:00
|
|
|
fprintf(stderr," -S - Run in server mode.\n");
|
|
|
|
fprintf(stderr," -f - Location of keyring file.\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr," -d - Search by Direct Inward Dial (DID) number.\n");
|
|
|
|
fprintf(stderr," -s - Search by Subscriber ID (SID) number.\n");
|
|
|
|
fprintf(stderr," -p - Specify additional DNA nodes to query.\n");
|
|
|
|
fprintf(stderr," -P - Authenticate using the supplied pin.\n");
|
2011-12-17 01:41:32 +00:00
|
|
|
fprintf(stderr," -r - Enable Rhizome store-and-forward transport using the specified data store directory.\n");
|
|
|
|
fprintf(stderr," To limit the storage: echo space=[KB] > path/rhizome.conf\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr," -R - Read a variable value.\n");
|
|
|
|
fprintf(stderr," -O - Place read variable value into files using argument as a template.\n");
|
|
|
|
fprintf(stderr," The following template codes can be used (interpretted by sprintf):\n");
|
|
|
|
fprintf(stderr," %%1$s - Subscriber ID\n");
|
|
|
|
fprintf(stderr," %%2$d - Variable ID (0-255)\n");
|
|
|
|
fprintf(stderr," %%3$d - Variable instance number (0-255)\n");
|
|
|
|
fprintf(stderr," -W - Write a variable value, keeping previous values.\n");
|
|
|
|
fprintf(stderr," -U - Update a variable value, replacing the previous value.\n");
|
|
|
|
fprintf(stderr," -D - Delete a variable value.\n");
|
|
|
|
fprintf(stderr," $value means interpret value as hexidecimal bytes.\n");
|
|
|
|
fprintf(stderr," @value means read value from file called value.\n");
|
|
|
|
fprintf(stderr," -C - Request the creation of a new subscriber with the specified DID.\n");
|
|
|
|
fprintf(stderr," -t - Specify the request timeout period.\n");
|
2011-08-10 13:38:59 +00:00
|
|
|
fprintf(stderr," -G - Offer gateway services. Argument specifies locations of necessary files.\n");
|
|
|
|
fprintf(stderr," Use -G [potato|android|custom:...] to set defaults for your device type.\n");
|
2011-08-08 06:41:05 +00:00
|
|
|
fprintf(stderr," -N - Specify one or more interfaces for the DNA overlay mesh to operate.\n");
|
2011-08-12 07:58:25 +00:00
|
|
|
fprintf(stderr," Interface specifications take the form <+|->[interface[=type][,...]\n");
|
|
|
|
fprintf(stderr," e.g., -N -en0,+ to use all interfaces except en0\n");
|
2010-07-13 12:15:46 +00:00
|
|
|
fprintf(stderr,"\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2011-06-08 02:44:16 +00:00
|
|
|
#ifndef DNA_NO_MAIN
|
2011-10-02 09:58:08 +00:00
|
|
|
char *exec_args[128];
|
|
|
|
int exec_argc=0;
|
2012-03-04 22:57:31 +00:00
|
|
|
|
|
|
|
int servalShutdown=0;
|
|
|
|
|
2012-03-16 22:32:37 +00:00
|
|
|
char *thisinstancepath=NULL;
|
|
|
|
char *serval_instancepath()
|
|
|
|
{
|
|
|
|
if (thisinstancepath) return thisinstancepath;
|
|
|
|
char *instancepath=getenv("SERVALINSTANCE_PATH");
|
|
|
|
if (!instancepath) instancepath=DEFAULT_INSTANCE_PATH;
|
|
|
|
return instancepath;
|
|
|
|
}
|
|
|
|
|
2012-03-29 03:37:07 +00:00
|
|
|
int form_serval_instance_path(char *buf, size_t bufsiz, const char *path)
|
|
|
|
{
|
|
|
|
if (snprintf(buf, bufsiz, "%s/%s", serval_instancepath(), path) < bufsiz)
|
|
|
|
return 1;
|
|
|
|
fprintf(stderr, "Cannot form pathname \"%s/%s\" -- buffer too small (%lu bytes)", serval_instancepath(), path, (unsigned long)bufsiz);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-29 04:33:17 +00:00
|
|
|
int create_serval_instance_dir() {
|
|
|
|
const char *instancepath = serval_instancepath();
|
|
|
|
if (mkdir(instancepath, 0700) == -1) {
|
|
|
|
if (errno == EEXIST) {
|
|
|
|
DIR *d = opendir(instancepath);
|
|
|
|
if (!d) {
|
|
|
|
WHYF("Cannot access %s", instancepath);
|
|
|
|
perror("opendir");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
closedir(d);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
WHYF("Cannot mkdir %s", instancepath);
|
|
|
|
perror("mkdir");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-04 22:57:31 +00:00
|
|
|
void servalShutdownCleanly()
|
|
|
|
{
|
|
|
|
WHY("Shutting down as requested.");
|
|
|
|
/* Try to remove shutdown and PID files and exit */
|
|
|
|
char filename[1024];
|
2012-03-29 03:37:07 +00:00
|
|
|
if (FORM_SERVAL_INSTANCE_PATH(filename, "doshutdown")) {
|
2012-03-20 16:57:47 +00:00
|
|
|
unlink(filename);
|
2012-03-29 03:37:07 +00:00
|
|
|
}
|
|
|
|
if (FORM_SERVAL_INSTANCE_PATH(filename, "serval.pid")) {
|
|
|
|
unlink(filename);
|
|
|
|
}
|
|
|
|
if (mdp_client_socket==-1) {
|
|
|
|
if (FORM_SERVAL_INSTANCE_PATH(filename, "mdp.socket")) {
|
|
|
|
unlink(filename);
|
|
|
|
}
|
2012-03-20 16:57:47 +00:00
|
|
|
} else {
|
|
|
|
overlay_mdp_client_done();
|
|
|
|
}
|
2012-03-04 22:57:31 +00:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2011-10-02 09:58:08 +00:00
|
|
|
void signal_handler( int signal ) {
|
2012-03-04 23:09:11 +00:00
|
|
|
|
|
|
|
if (signal==SIGQUIT) servalShutdownCleanly();
|
2012-03-04 22:57:31 +00:00
|
|
|
|
2012-03-04 23:09:11 +00:00
|
|
|
if (signal==SIGHUP||signal==SIGINT) {
|
2012-03-04 22:57:31 +00:00
|
|
|
/* Shut down.
|
|
|
|
The shutting down should be done from the main-line code rather than here,
|
|
|
|
so we first try to tell the mainline code to do so. If, however, this is
|
|
|
|
not the first time we have been asked to shut down, then we will do it here. */
|
|
|
|
if (servalShutdown) {
|
|
|
|
/* We have been asked before, so shut down cleanly */
|
|
|
|
servalShutdownCleanly();
|
|
|
|
} else {
|
|
|
|
WHY("Asking Serval process to shutdown cleanly");
|
|
|
|
servalShutdown=1;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-10-02 09:58:08 +00:00
|
|
|
/* oops - caught a bad signal -- exec() ourselves fresh */
|
2012-02-17 01:46:43 +00:00
|
|
|
char signalName[64];
|
|
|
|
snprintf(signalName,63,"signal %d",signal); signalName[63]=0;
|
|
|
|
switch(signal) {
|
|
|
|
#ifdef SIGHUP
|
|
|
|
case SIGHUP: snprintf(signalName,63,"SIG %s (%d)","hangup",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGINT
|
|
|
|
case SIGINT: snprintf(signalName,63,"SIG %s (%d)","interrupt",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGQUIT
|
|
|
|
case SIGQUIT: snprintf(signalName,63,"SIG %s (%d)","quit",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGILL
|
|
|
|
case SIGILL: snprintf(signalName,63,"SIG %s (%d)","illegal instruction (not reset when caught)",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGTRAP
|
|
|
|
case SIGTRAP: snprintf(signalName,63,"SIG %s (%d)","trace trap (not reset when caught)",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGABRT
|
|
|
|
case SIGABRT: snprintf(signalName,63,"SIG %s (%d)","abort()",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGPOLL
|
|
|
|
case SIGPOLL: snprintf(signalName,63,"SIG %s (%d)","pollable event ([XSR] generated, not supported)",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGEMT
|
|
|
|
case SIGEMT: snprintf(signalName,63,"SIG %s (%d)","EMT instruction",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGFPE
|
|
|
|
case SIGFPE: snprintf(signalName,63,"SIG %s (%d)","floating point exception",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGKILL
|
|
|
|
case SIGKILL: snprintf(signalName,63,"SIG %s (%d)","kill (cannot be caught or ignored)",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGBUS
|
|
|
|
case SIGBUS: snprintf(signalName,63,"SIG %s (%d)","bus error",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGSEGV
|
|
|
|
case SIGSEGV: snprintf(signalName,63,"SIG %s (%d)","segmentation violation",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGSYS
|
|
|
|
case SIGSYS: snprintf(signalName,63,"SIG %s (%d)","bad argument to system call",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGPIPE
|
|
|
|
case SIGPIPE: snprintf(signalName,63,"SIG %s (%d)","write on a pipe with no one to read it",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGALRM
|
|
|
|
case SIGALRM: snprintf(signalName,63,"SIG %s (%d)","alarm clock",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGTERM
|
|
|
|
case SIGTERM: snprintf(signalName,63,"SIG %s (%d)","software termination signal from kill",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGURG
|
|
|
|
case SIGURG: snprintf(signalName,63,"SIG %s (%d)","urgent condition on IO channel",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGSTOP
|
|
|
|
case SIGSTOP: snprintf(signalName,63,"SIG %s (%d)","sendable stop signal not from tty",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGTSTP
|
|
|
|
case SIGTSTP: snprintf(signalName,63,"SIG %s (%d)","stop signal from tty",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGCONT
|
|
|
|
case SIGCONT: snprintf(signalName,63,"SIG %s (%d)","continue a stopped process",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGCHLD
|
|
|
|
case SIGCHLD: snprintf(signalName,63,"SIG %s (%d)","to parent on child stop or exit",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGTTIN
|
|
|
|
case SIGTTIN: snprintf(signalName,63,"SIG %s (%d)","to readers pgrp upon background tty read",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGTTOU
|
|
|
|
case SIGTTOU: snprintf(signalName,63,"SIG %s (%d)","like TTIN for output if (tp->t_local<OSTOP)",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGIO
|
2012-02-27 01:45:52 +00:00
|
|
|
#if SIGIO != SIGPOLL
|
2012-02-17 01:46:43 +00:00
|
|
|
case SIGIO: snprintf(signalName,63,"SIG %s (%d)","input/output possible signal",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
2012-02-27 01:45:52 +00:00
|
|
|
#endif
|
2012-02-17 01:46:43 +00:00
|
|
|
#ifdef SIGXCPU
|
|
|
|
case SIGXCPU: snprintf(signalName,63,"SIG %s (%d)","exceeded CPU time limit",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGXFSZ
|
|
|
|
case SIGXFSZ: snprintf(signalName,63,"SIG %s (%d)","exceeded file size limit",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGVTALRM
|
|
|
|
case SIGVTALRM: snprintf(signalName,63,"SIG %s (%d)","virtual time alarm",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGPROF
|
|
|
|
case SIGPROF: snprintf(signalName,63,"SIG %s (%d)","profiling time alarm",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGWINCH
|
|
|
|
case SIGWINCH: snprintf(signalName,63,"SIG %s (%d)","window size changes",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGINFO
|
|
|
|
case SIGINFO: snprintf(signalName,63,"SIG %s (%d)","information request",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGUSR1
|
|
|
|
case SIGUSR1: snprintf(signalName,63,"SIG %s (%d)","user defined signal 1",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef SIGUSR2
|
|
|
|
case SIGUSR2: snprintf(signalName,63,"SIG %s (%d)","user defined signal 2",signal);
|
2012-02-23 01:26:46 +00:00
|
|
|
break;
|
2012-02-17 01:46:43 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
signalName[63]=0;
|
|
|
|
fprintf(stderr,"Caught terminal signal %s -- respawning.\n",signalName);
|
2011-10-02 10:48:16 +00:00
|
|
|
if (sock>-1) close(sock);
|
2012-02-17 01:46:43 +00:00
|
|
|
int i;
|
|
|
|
for(i=0;i<overlay_interface_count;i++)
|
|
|
|
if (overlay_interfaces[i].fd>-1)
|
|
|
|
close(overlay_interfaces[i].fd);
|
2011-10-02 09:58:08 +00:00
|
|
|
execv(exec_args[0],exec_args);
|
|
|
|
/* Quit if the exec() fails */
|
|
|
|
exit(-3);
|
|
|
|
}
|
|
|
|
|
2012-02-23 02:08:57 +00:00
|
|
|
int setVerbosity(char *optarg) {
|
|
|
|
long long old_debug=debug;
|
|
|
|
debug=strtoll(optarg,NULL,10);
|
|
|
|
if (strstr(optarg,"interfaces")) debug|=DEBUG_OVERLAYINTERFACES;
|
2012-04-13 21:08:11 +00:00
|
|
|
if (strstr(optarg,"rx")) debug|=DEBUG_PACKETRX;
|
|
|
|
if (strstr(optarg,"tx")) debug|=DEBUG_PACKETTX;
|
2012-02-23 02:08:57 +00:00
|
|
|
if (strstr(optarg,"verbose")) debug|=DEBUG_VERBOSE;
|
|
|
|
if (strstr(optarg,"verbio")) debug|=DEBUG_VERBOSE_IO;
|
|
|
|
if (strstr(optarg,"peers")) debug|=DEBUG_PEERS;
|
|
|
|
if (strstr(optarg,"dnaresponses")) debug|=DEBUG_DNARESPONSES;
|
|
|
|
if (strstr(optarg,"dnarequests")) debug|=DEBUG_DNAREQUESTS;
|
|
|
|
if (strstr(optarg,"simulation")) debug|=DEBUG_SIMULATION;
|
|
|
|
if (strstr(optarg,"dnavars")) debug|=DEBUG_DNAVARS;
|
|
|
|
if (strstr(optarg,"packetformats")) debug|=DEBUG_PACKETFORMATS;
|
2012-04-15 20:36:43 +00:00
|
|
|
if (strstr(optarg,"packetconstruction")) debug|=DEBUG_PACKETCONSTRUCTION;
|
2012-02-23 02:08:57 +00:00
|
|
|
if (strstr(optarg,"gateway")) debug|=DEBUG_GATEWAY;
|
|
|
|
if (strstr(optarg,"hlr")) debug|=DEBUG_HLR;
|
|
|
|
if (strstr(optarg,"sockio")) debug|=DEBUG_IO;
|
|
|
|
if (strstr(optarg,"frames")) debug|=DEBUG_OVERLAYFRAMES;
|
|
|
|
if (strstr(optarg,"abbreviations")) debug|=DEBUG_OVERLAYABBREVIATIONS;
|
|
|
|
if (strstr(optarg,"routing")) debug|=DEBUG_OVERLAYROUTING;
|
|
|
|
if (strstr(optarg,"security")) debug|=DEBUG_SECURITY;
|
|
|
|
if (strstr(optarg,"rhizome")) debug|=DEBUG_RHIZOME;
|
2012-04-13 20:56:20 +00:00
|
|
|
if (strstr(optarg,"norhizome"))
|
|
|
|
{ debug|=DEBUG_DISABLERHIZOME; debug&=~DEBUG_RHIZOME; }
|
2012-02-23 02:08:57 +00:00
|
|
|
if (strstr(optarg,"filesync")) debug|=DEBUG_RHIZOMESYNC;
|
|
|
|
if (strstr(optarg,"monitorroutes")) debug|=DEBUG_OVERLAYROUTEMONITOR;
|
|
|
|
if (strstr(optarg,"queues")) debug|=DEBUG_QUEUES;
|
|
|
|
if (strstr(optarg,"broadcasts")) debug|=DEBUG_BROADCASTS;
|
|
|
|
|
|
|
|
if (old_debug==debug) {
|
|
|
|
fprintf(stderr,"WARNING: Option '%s' had no effect on existing debug/verbosity level.\n",
|
|
|
|
optarg);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
int main(int argc,char **argv)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
char *pin=NULL;
|
|
|
|
char *did=NULL;
|
|
|
|
char *sid=NULL;
|
2012-04-12 23:55:03 +00:00
|
|
|
char *keyring_file=NULL;
|
2010-07-13 12:15:46 +00:00
|
|
|
int instance=-1;
|
|
|
|
int foregroundMode=0;
|
|
|
|
|
2012-02-16 14:09:09 +00:00
|
|
|
memabuseInit();
|
|
|
|
|
2011-03-30 05:04:23 +00:00
|
|
|
#if defined WIN32
|
|
|
|
WSADATA wsa_data;
|
|
|
|
WSAStartup(MAKEWORD(1,1), &wsa_data);
|
2011-10-02 09:58:08 +00:00
|
|
|
#else
|
|
|
|
/* Catch sigsegv and other crash signals so that we can relaunch ourselves */
|
|
|
|
|
|
|
|
for(exec_argc=0;exec_argc<argc;exec_argc++)
|
|
|
|
exec_args[exec_argc]=strdup(argv[exec_argc]);
|
|
|
|
exec_args[exec_argc]=0;
|
|
|
|
|
|
|
|
signal( SIGSEGV, signal_handler );
|
|
|
|
signal( SIGFPE, signal_handler );
|
|
|
|
signal( SIGILL, signal_handler );
|
|
|
|
signal( SIGBUS, signal_handler );
|
2012-03-04 22:57:31 +00:00
|
|
|
signal( SIGABRT, signal_handler );
|
|
|
|
|
2012-03-04 23:09:11 +00:00
|
|
|
/* Catch SIGHUP etc so that we can respond to requests to do things */
|
2012-03-04 22:57:31 +00:00
|
|
|
signal( SIGHUP, signal_handler );
|
2012-03-04 23:09:11 +00:00
|
|
|
signal( SIGINT, signal_handler );
|
|
|
|
signal( SIGQUIT, signal_handler );
|
2011-03-30 05:04:23 +00:00
|
|
|
#endif
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
srandomdev();
|
|
|
|
|
2012-02-23 02:08:57 +00:00
|
|
|
if (argv[1]&&argv[1][0]!='-') {
|
|
|
|
/* First argument doesn't start with a dash, so assume it is for the new command line
|
|
|
|
parser. */
|
|
|
|
|
|
|
|
/* Don't include name of program in arguments */
|
|
|
|
int return_value=parseCommandLine(argc-1,&argv[1]);
|
|
|
|
|
|
|
|
#if defined WIN32
|
|
|
|
WSACleanup();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return return_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr,
|
|
|
|
"WARNING: The use of the old command line structure is being deprecated.\n"
|
|
|
|
" Type '%s help' to learn about the new command line structure.\n",
|
|
|
|
argv[0]);
|
|
|
|
|
2012-04-12 23:55:03 +00:00
|
|
|
while((c=getopt(argc,argv,"Ab:B:E:G:I:Sf:d:i:l:L:mnp:P:r:s:t:v:R:W:U:D:CO:M:N:")) != -1 )
|
2010-07-13 12:15:46 +00:00
|
|
|
{
|
|
|
|
switch(c)
|
|
|
|
{
|
2012-04-12 23:55:03 +00:00
|
|
|
case 'S': serverMode=1; break;
|
2011-12-17 01:41:32 +00:00
|
|
|
case 'r': /* Enable rhizome */
|
|
|
|
if (rhizome_datastore_path) return WHY("-r specified more than once");
|
|
|
|
rhizome_datastore_path=optarg;
|
|
|
|
rhizome_opendb();
|
2012-04-12 23:55:03 +00:00
|
|
|
/* Also set keyring file to be in the Rhizome directory, to save the need to specify it
|
2012-01-08 22:36:35 +00:00
|
|
|
separately. */
|
2012-04-02 08:12:40 +00:00
|
|
|
char temp[1024];
|
2012-04-12 23:55:03 +00:00
|
|
|
if (snprintf(temp, sizeof(temp), "%s/serval.keyring", optarg)
|
|
|
|
>= sizeof(temp))
|
2012-01-08 22:36:35 +00:00
|
|
|
exit(WHY("Rhizome directory name too long."));
|
2012-04-12 23:55:03 +00:00
|
|
|
keyring_file = strdup(temp);
|
2011-12-17 01:41:32 +00:00
|
|
|
break;
|
2011-12-18 19:16:34 +00:00
|
|
|
case 'M': /* Distribute specified manifest and file pair using Rhizome. */
|
|
|
|
/* This option assumes that the manifest is locally produced, and will
|
|
|
|
create any appropriate signatures, replacing any old signatures on the
|
|
|
|
manifest.
|
|
|
|
A different calling would be required to import an existing pre-signed
|
|
|
|
manifest */
|
2012-04-13 08:17:20 +00:00
|
|
|
return rhizome_bundle_import(NULL, NULL, optarg,
|
2011-12-18 19:16:34 +00:00
|
|
|
NULL /* no groups - XXX should allow them */,
|
2012-01-03 06:05:02 +00:00
|
|
|
255 /* ttl - XXX should read from somewhere,
|
|
|
|
e.g., bar if being imported */,
|
2011-12-18 19:16:34 +00:00
|
|
|
0 /* int verifyP */,
|
|
|
|
1 /* int checkFileP */,
|
|
|
|
1 /* int signP */);
|
|
|
|
break;
|
2011-12-04 07:18:51 +00:00
|
|
|
case 'm': returnMultiVars=1; break;
|
2011-08-08 06:41:05 +00:00
|
|
|
case 'N': /* Ask for overlay network to setup one or more interfaces */
|
|
|
|
if (overlay_interface_args(optarg))
|
|
|
|
return WHY("Invalid interface specification(s) passed to -N");
|
2011-08-08 14:41:46 +00:00
|
|
|
overlayMode=1;
|
2011-08-08 06:41:05 +00:00
|
|
|
break;
|
2011-05-06 02:27:33 +00:00
|
|
|
case 'G': /* Offer gateway services */
|
2012-02-23 02:08:57 +00:00
|
|
|
gatewayspec=strdup(optarg);
|
2011-08-10 13:38:59 +00:00
|
|
|
if(prepareGateway(gatewayspec)) return usage("Invalid gateway specification");
|
2012-02-23 02:08:57 +00:00
|
|
|
break;
|
2010-07-13 12:15:46 +00:00
|
|
|
case 'n': /* don't detach from foreground in server mode */
|
|
|
|
foregroundMode=1; break;
|
|
|
|
case 'b': /* talk peers on a BATMAN mesh */
|
|
|
|
batman_socket=strdup(optarg);
|
|
|
|
break;
|
2011-03-21 02:38:35 +00:00
|
|
|
case 'l': /* talk peers on a BATMAN mesh */
|
|
|
|
batman_peerfile=strdup(optarg);
|
|
|
|
break;
|
2011-05-05 09:10:38 +00:00
|
|
|
case 'L':
|
|
|
|
instrumentation_file=strdup(optarg);
|
|
|
|
break;
|
2010-07-13 12:15:46 +00:00
|
|
|
case 'B': /* Set simulated Bit Error Rate for bench-testing */
|
|
|
|
simulatedBER=atof(optarg);
|
|
|
|
fprintf(stderr,"WARNING: Bit error injection enabled -- this will cause packet loss and is intended only for testing.\n");
|
|
|
|
break;
|
|
|
|
case 'i':
|
|
|
|
instance=atoi(optarg);
|
|
|
|
if (instance<-1||instance>255) usage("Illegal variable instance ID.");
|
|
|
|
break;
|
|
|
|
case 'f':
|
2012-04-12 23:55:03 +00:00
|
|
|
if (clientMode) usage("Only servers use keyring files");
|
|
|
|
keyring_file=strdup(optarg);
|
2010-07-13 12:15:46 +00:00
|
|
|
break;
|
|
|
|
case 'p': /* additional peers to query */
|
|
|
|
if (additionalPeer(optarg)) exit(-3);
|
|
|
|
break;
|
|
|
|
case 'P': /* Supply pin */
|
|
|
|
pin=strdup(optarg);
|
|
|
|
clientMode=1;
|
|
|
|
break;
|
|
|
|
case 'd': /* Ask by DID */
|
|
|
|
clientMode=1;
|
|
|
|
did=strdup(optarg);
|
|
|
|
break;
|
|
|
|
case 's': /* Ask by subscriber ID */
|
|
|
|
clientMode=1;
|
|
|
|
sid=strdup(optarg);
|
|
|
|
break;
|
|
|
|
case 't': /* request timeout (ms) */
|
|
|
|
timeout=atoi(optarg);
|
|
|
|
break;
|
2012-01-10 05:26:40 +00:00
|
|
|
case 'v': /* set verbosity */
|
2012-02-23 02:08:57 +00:00
|
|
|
setVerbosity(optarg);
|
2010-07-13 12:15:46 +00:00
|
|
|
break;
|
2011-03-22 06:01:02 +00:00
|
|
|
case 'A': /* get address (IP or otherwise) of a given peer */
|
|
|
|
peerAddress(did,sid,3 /* 1 = print list of addresses to stdout, 2 = set peer list to responders */);
|
|
|
|
break;
|
2010-07-13 12:15:46 +00:00
|
|
|
case 'R': /* read a variable */
|
|
|
|
{
|
|
|
|
unsigned char buffer[65535];
|
|
|
|
int len=0;
|
|
|
|
requestItem(did,sid,(char *)optarg,instance,buffer,sizeof(buffer),&len,NULL);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'W': /* write a variable */
|
|
|
|
{
|
|
|
|
int var_id;
|
|
|
|
unsigned char value[65536];
|
|
|
|
int value_len=65535;
|
|
|
|
if (parseAssignment((unsigned char *)optarg,&var_id,value,&value_len)) return -1;
|
|
|
|
value[value_len]=0;
|
2012-02-05 05:45:19 +00:00
|
|
|
return writeItem(did?did:sid,var_id,instance,value,0,value_len,SET_NOREPLACE,-1,NULL);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'U': /* write or update a variable */
|
|
|
|
{
|
|
|
|
int var_id;
|
|
|
|
unsigned char value[65536];
|
|
|
|
int value_len=65535;
|
|
|
|
if (parseAssignment((unsigned char *)optarg,&var_id,value,&value_len)) return -1;
|
|
|
|
value[value_len]=0;
|
2012-02-05 05:45:19 +00:00
|
|
|
return writeItem(did?did:sid,var_id,instance,value,0,value_len,SET_REPLACE,-1,NULL);
|
2010-07-13 12:15:46 +00:00
|
|
|
}
|
|
|
|
break;
|
2012-04-12 23:55:03 +00:00
|
|
|
case 'C': /* create a new keyring entry */
|
|
|
|
return WHY("Entries in new keyring format must be used with new command line framework.");
|
2010-07-13 12:15:46 +00:00
|
|
|
break;
|
|
|
|
case 'O': /* output to templated files */
|
|
|
|
if (outputtemplate) usage("You can only specify -O once");
|
|
|
|
outputtemplate=strdup(optarg);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage("Invalid option");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind<argc) usage("Extraneous options at end of command");
|
|
|
|
|
2012-04-12 23:55:03 +00:00
|
|
|
if (keyring_file&&clientMode) usage("Only servers use backing files");
|
2010-07-13 12:15:46 +00:00
|
|
|
if (serverMode&&clientMode) usage("You asked me to be both server and client. That's silly.");
|
2012-04-12 23:55:03 +00:00
|
|
|
if (serverMode) return server(keyring_file,foregroundMode);
|
|
|
|
if (!clientMode) usage("Serval server and client utility.");
|
2010-07-13 12:15:46 +00:00
|
|
|
|
2011-03-30 05:04:23 +00:00
|
|
|
#if defined WIN32
|
|
|
|
WSACleanup();
|
|
|
|
#endif
|
|
|
|
|
2010-07-13 12:15:46 +00:00
|
|
|
/* Client mode: */
|
|
|
|
return 0;
|
|
|
|
}
|
2011-06-08 02:44:16 +00:00
|
|
|
#endif
|
|
|
|
|
2011-08-08 06:41:05 +00:00
|
|
|
long long parse_quantity(char *q)
|
|
|
|
{
|
|
|
|
int m;
|
|
|
|
char units[80];
|
|
|
|
|
|
|
|
if (strlen(q)>=80) return WHY("quantity string >=80 characters");
|
|
|
|
|
|
|
|
if (sscanf(q,"%d%s",&m,units)==2)
|
|
|
|
{
|
|
|
|
if (units[1]) return WHY("Units should be single character");
|
|
|
|
switch(units[0])
|
|
|
|
{
|
|
|
|
case 'k': return m*1000LL;
|
|
|
|
case 'K': return m*1024LL;
|
|
|
|
case 'm': return m*1000LL*1000LL;
|
|
|
|
case 'M': return m*1024LL*1024LL;
|
|
|
|
case 'g': return m*1000LL*1000LL*1000LL;
|
|
|
|
case 'G': return m*1024LL*1024LL*1024LL;
|
|
|
|
default:
|
|
|
|
return WHY("Illegal unit: should be k,K,m,M,g, or G.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sscanf(q,"%d",&m)==1)
|
|
|
|
{
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return WHY("Could not parse quantity");
|
|
|
|
}
|
|
|
|
}
|