Refactor JNI out of CLI and server main loop

The CLI and server main loop now have no conditional JNI code.  All JNI
code has been moved into separate source files, which #include the new
"jni_common.h" instead of <jni.h>.  The "cli.h" header no longer
includes <jni.h>, so the rest of the Serval source code is now
unaffected by JNI definitions.

The 'cf_limbo' global variable is now thread-local, so that each thread
has its own independent copy of the loaded configuration.  The JNI
server entry point now calls cf_init() once.  The new 'cf_initialised'
flag prevents clobbering the config state by redundant calls to
cf_init().

The CLI "stop" command now sends SIGHUP to the specific thread in which
the server is running.  This is achieved by writing the PID and TID
(Linux Thread ID) into the pidfile, separated by a space, on systems
that support the Linux gettid() and tgkill() system calls.  The server's
signal handler has been overhauled, and its logging improved.
This commit is contained in:
Andrew Bettison 2016-10-12 17:08:26 +10:30
parent 41b3e304be
commit c8bf8a7733
38 changed files with 1916 additions and 1185 deletions

View File

@ -22,6 +22,7 @@ ALL_SOURCES = \
$(SERVAL_CLIENT_SOURCES) \
$(MDP_CLIENT_SOURCES) \
$(SERVAL_DAEMON_SOURCES) \
$(SERVAL_DAEMON_JNI_SOURCES) \
$(TEST_SOURCES) \
$(SERVAL_LIB_SOURCES) \
$(MONITOR_CLIENT_SRCS) \
@ -31,7 +32,8 @@ ALL_SOURCES = \
SERVAL_DAEMON_OBJS = \
$(addprefix $(OBJSDIR_SERVALD)/, $(SERVAL_CLIENT_SOURCES:.c=.o)) \
$(addprefix $(OBJSDIR_SERVALD)/, $(MDP_CLIENT_SOURCES:.c=.o)) \
$(addprefix $(OBJSDIR_SERVALD)/, $(SERVAL_DAEMON_SOURCES:.c=.o))
$(addprefix $(OBJSDIR_SERVALD)/, $(SERVAL_DAEMON_SOURCES:.c=.o)) \
$(addprefix $(OBJSDIR_SERVALD)/, $(SERVAL_DAEMON_JNI_SOURCES:.c=.o))
SQLITE3_OBJS = \
$(addprefix $(OBJSDIR_SERVALD)/, $(notdir $(SQLITE3_SOURCES:.c=.o)))
SERVALD_OBJS = \

88
cli.c
View File

@ -1,6 +1,7 @@
/*
Serval DNA command-line functions
Serval DNA command-line interface
Copyright (C) 2010-2013 Serval Project Inc.
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -17,20 +18,13 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <assert.h>
#include "cli.h"
#include "constants.h"
#include "serval_types.h"
#include "rhizome_types.h"
#include "fdqueue.h"
#include "os.h"
#include "str.h"
#include "strbuf_helpers.h"
#include "log.h"
#include "debug.h"
#include "str.h"
#include "numeric_str.h"
#include "strbuf_helpers.h"
#include "dataformats.h"
int cli_usage(const struct cli_schema *commands, const struct cli_schema *end_commands, XPRINTF xpf)
{
@ -360,6 +354,80 @@ int _cli_arg(struct __sourceloc __whence, const struct cli_parsed *parsed, char
return 1;
}
/* Output primitive dispatch.
*/
void cli_delim(struct cli_context *context, const char *opt)
{
(context->vtable->delim)(context, opt);
}
void cli_write(struct cli_context *context, const char *buf, size_t len)
{
(context->vtable->write)(context, buf, len);
}
void cli_puts(struct cli_context *context, const char *str)
{
(context->vtable->puts)(context, str);
}
void cli_printf(struct cli_context *context, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
(context->vtable->vprintf)(context, fmt, ap);
va_end(ap);
}
void cli_put_long(struct cli_context *context, int64_t value, const char *delim_opt)
{
(context->vtable->put_long)(context, value, delim_opt);
}
void cli_put_string(struct cli_context *context, const char *value, const char *delim_opt)
{
(context->vtable->put_string)(context, value, delim_opt);
}
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim_opt)
{
(context->vtable->put_hexvalue)(context, value, length, delim_opt);
}
void cli_put_blob(struct cli_context *context, const unsigned char *blob, int length, const char *delim_opt)
{
(context->vtable->put_blob)(context, blob, length, delim_opt);
}
void cli_start_table(struct cli_context *context, size_t column_count, const char *column_names[])
{
(context->vtable->start_table)(context, column_count, column_names);
}
void cli_end_table(struct cli_context *context, size_t row_count)
{
(context->vtable->end_table)(context, row_count);
}
void cli_field_name(struct cli_context *context, const char *name, const char *delim_opt)
{
(context->vtable->field_name)(context, name, delim_opt);
}
void cli_flush(struct cli_context *UNUSED(context))
{
(context->vtable->flush)(context);
}
/* Parsing validator functions.
*/
#include "numeric_str.h"
#include "serval_types.h"
#include "dataformats.h"
#include "rhizome_types.h"
int cli_lookup_did(const char *text)
{
return text[0] == '\0' || strcmp(text, "*") == 0 || str_is_did(text);

157
cli.h
View File

@ -1,53 +1,34 @@
/*
Serval command line parsing and processing.
Copyright (C) 2012,2013 Serval Project Inc.
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.
*/
Serval DNA command-line interface
Copyright (C) 2010-2013 Serval Project Inc.
Copyright (C) 2016 Flinders University
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.
*/
#ifndef __SERVAL_DNA__CLI_H
#define __SERVAL_DNA__CLI_H
#include <stdint.h>
#ifdef HAVE_JNI_H
#include <jni.h>
// Stop OpenJDK 7 from foisting their UNUSED() macro on us in <jni_md.h>
#ifdef UNUSED
# undef UNUSED
#endif
int Throw(JNIEnv *env, const char *class, const char *msg);
#endif
#include "xprintf.h"
#include "log.h"
#define COMMAND_LINE_MAX_LABELS (16)
struct cli_parsed;
struct cli_context{
#ifdef HAVE_JNI_H
JNIEnv *jni_env;
int jni_exception;
jobject jniResults;
char *outv_buffer;
char *outv_current;
char *outv_limit;
#endif
void *context;
};
struct cli_context;
struct cli_schema {
int (*function)(const struct cli_parsed *parsed, struct cli_context *context);
@ -98,6 +79,8 @@ int cli_invoke(const struct cli_parsed *parsed, struct cli_context *context);
#define cli_arg(parsed, label, dst, validator, defaultvalue) _cli_arg(__WHENCE__, parsed, label, dst, validator, defaultvalue)
int _cli_arg(struct __sourceloc __whence, const struct cli_parsed *parsed, char *label, const char **dst, int (*validator)(const char *arg), char *defaultvalue);
/* Argument parsing validator runctions.
*/
int cli_lookup_did(const char *text);
int cli_path_regular(const char *arg);
int cli_absolute_path(const char *arg);
@ -111,4 +94,102 @@ int cli_interval_ms(const char *arg);
int cli_uint(const char *arg);
int cli_optional_did(const char *text);
/* Output functions. Every command that is invoked via the CLI must use
* exclusively the following primitives to send its response.
*
* The CLI output is organised as a sequence of 'fields'. The
*/
/* Terminate the current output field, so that the next cli_ output function
* will start appending to a new field.
*/
void cli_delim(struct cli_context *context, const char *opt);
/* Write a buffer of data to the current field, starting a new field if necessary.
*/
void cli_write(struct cli_context *context, const char *buf, size_t len);
/* Write a null-terminated string to the current field, starting a new field if
* necessary. The terminating null is not included.
*/
void cli_puts(struct cli_context *context, const char *str);
/* Write a formatted string to the current field, starting a new field if
* necessary.
*/
void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( __ATTRIBUTE_format(printf,2,3) ));
/* Write a field consisting of a single long integer. May FATAL if the current field
* has already been written to.
*/
void cli_put_long(struct cli_context *context, int64_t value, const char *delim);
/* Write a field consisting of a single string. May FATAL if the current field
* has already been written to.
*/
void cli_put_string(struct cli_context *context, const char *value, const char *delim);
/* Write a field consisting of a buffer of data that should be represented in
* ASCII hex format, eg, SID, Bundle ID, crypto key. May FATAL if the current
* field has already been written to.
*/
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim);
/* Write a field consisting of a buffer of binary data in unspecified format,
* ie, not necessarily text. FATAL if the current field has already been
* written to.
*/
void cli_put_blob(struct cli_context *context, const unsigned char *blob, int length, const char *delim);
/* Write a list of column headers. The column headers must be followed by N *
* column_count fields, where N >= 0 is the number of rows in the table. After
* the last field, cli_end_table(N) must be called.
*/
void cli_start_table(struct cli_context *context, size_t column_count, const char *column_names[]);
/* Write a count of the number of rows just written to a table that was started
* with cli_start_table(). This terminates the data portion of the table; no
* more rows may be written after this.
*/
void cli_end_table(struct cli_context *context, size_t row_count);
/* Write a 'name' in a name-value field pair. This is used when writing an
* aggregate data item that could be represented using a struct in C. Each
* output field is prefixed with a text field containing its name; names are
* usually unique. This produces an even number of fields.
*/
void cli_field_name(struct cli_context *context, const char *name, const char *delim);
/* Force all fields written so far to be sent to the CLI client; this may not
* have any effect.
*/
void cli_flush(struct cli_context *context);
void cli_cleanup();
/* CLI encapulation. Every interface that can encapsulate the CLI must provide
* a vtable of operations that realise the above output primitives in terms of
* its own data channel.
*/
struct cli_vtable {
void (*delim)(struct cli_context *context, const char *opt);
void (*write)(struct cli_context *context, const char *buf, size_t len);
void (*puts)(struct cli_context *context, const char *str);
void (*vprintf)(struct cli_context *context, const char *fmt, va_list ap);
void (*put_long)(struct cli_context *context, int64_t value, const char *delim);
void (*put_string)(struct cli_context *context, const char *value, const char *delim);
void (*put_hexvalue)(struct cli_context *context, const unsigned char *value, size_t length, const char *delim);
void (*put_blob)(struct cli_context *context, const unsigned char *value, size_t length, const char *delim);
void (*start_table)(struct cli_context *context, size_t column_count, const char *column_names[]);
void (*end_table)(struct cli_context *context, size_t row_count);
void (*field_name)(struct cli_context *context, const char *name, const char *delim);
void (*flush)(struct cli_context *context);
};
struct cli_context {
void *context;
struct cli_vtable *vtable;
};
#endif // __SERVAL_DNA__CLI_H

135
cli_stdio.c Normal file
View File

@ -0,0 +1,135 @@
/*
Serval DNA command-line output primitives
Copyright (C) 2016 Flinders University
Copyright (C) 2014 Serval Project Inc.
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.
*/
#include <stdlib.h>
#include <inttypes.h> // for PRId64
#include "cli_stdio.h"
#include "log.h"
#include "str.h"
static FILE *stdio_fp(struct cli_context *context)
{
return ((struct cli_context_stdio *)(context->context))->fp;
}
static void cl_delim(struct cli_context *context, const char *opt)
{
// Simply writes a newline to standard output (or the value of the SERVALD_OUTPUT_DELIMITER env
// var if set).
const char *delim = getenv("SERVALD_OUTPUT_DELIMITER");
if (delim == NULL)
delim = opt ? opt : "\n";
fputs(delim, stdio_fp(context));
}
static void cl_write(struct cli_context *UNUSED(context), const char *buf, size_t len)
{
fwrite(buf, len, 1, stdio_fp(context));
}
static void cl_puts(struct cli_context *UNUSED(context), const char *str)
{
fputs(str, stdio_fp(context));
}
static void cl_vprintf(struct cli_context *UNUSED(context), const char *fmt, va_list ap)
{
if (vfprintf(stdio_fp(context), fmt, ap) < 0)
WHYF("vfprintf(%s,...) failed", alloca_str_toprint(fmt));
}
static void cl_printf(struct cli_context *context, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
cl_vprintf(context, fmt, ap);
va_end(ap);
}
static void cl_put_long(struct cli_context *context, int64_t value, const char *delim_opt)
{
cl_printf(context, "%" PRId64, value);
cl_delim(context, delim_opt);
}
static void cl_put_string(struct cli_context *context, const char *value, const char *delim_opt)
{
if (value)
cl_puts(context, value);
cl_delim(context, delim_opt);
}
static void cl_put_hexvalue(struct cli_context *context, const unsigned char *value, size_t length, const char *delim_opt)
{
if (value)
cl_puts(context, alloca_tohex(value, length));
cl_delim(context, delim_opt);
}
static void cl_put_blob(struct cli_context *context, const unsigned char *blob, size_t length, const char *delim_opt)
{
if (blob)
cl_write(context, (const char *)blob, length);
cl_delim(context, delim_opt);
}
static void cl_start_table(struct cli_context *context, size_t column_count, const char *column_names[])
{
cli_printf(context, "%zu", column_count);
cli_delim(context, "\n");
size_t i;
for (i = 0; i != column_count; ++i) {
cli_puts(context, column_names[i]);
if (i + 1 == column_count)
cli_delim(context, "\n");
else
cli_delim(context, ":");
}
}
static void cl_end_table(struct cli_context *UNUSED(context), size_t UNUSED(row_count))
{
}
static void cl_field_name(struct cli_context *context, const char *name, const char *delim_opt)
{
cli_puts(context, name);
cli_delim(context, delim_opt);
}
static void cl_flush(struct cli_context *UNUSED(context))
{
fflush(stdio_fp(context));
}
struct cli_vtable cli_vtable_stdio = {
.delim = cl_delim,
.write = cl_write,
.puts = cl_puts,
.vprintf = cl_vprintf,
.put_long = cl_put_long,
.put_string = cl_put_string,
.put_hexvalue = cl_put_hexvalue,
.put_blob = cl_put_blob,
.start_table = cl_start_table,
.end_table = cl_end_table,
.field_name = cl_field_name,
.flush = cl_flush
};

36
cli_stdio.h Normal file
View File

@ -0,0 +1,36 @@
/*
Serval DNA command-line output primitives
Copyright (C) 2016 Flinders University
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.
*/
/* CLI output primitive implementation that writes to a standard i/o stream
* like stdout.
*/
#ifndef __SERVAL_DNA__CLI_STDIO_H
#define __SERVAL_DNA__CLI_STDIO_H
#include <stdio.h>
#include "cli.h"
struct cli_context_stdio {
FILE *fp;
};
extern struct cli_vtable cli_vtable_stdio;
#endif // __SERVAL_DNA__CLI_STDIO_H

View File

@ -1,6 +1,7 @@
/*
Serval DNA command-line functions
Copyright (C) 2010-2013 Serval Project Inc.
Serval DNA command-line parsing
Copyright (C) 2014-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -17,22 +18,17 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "commandline.h"
#include "serval.h"
#include "conf.h"
#include "mdp_client.h"
#include "cli.h"
#include "keyring.h"
#include "dataformats.h"
#include "commandline.h"
#include "server.h"
#include "str.h"
#include "cli_stdio.h"
DEFINE_CMD(commandline_usage,CLIFLAG_PERMISSIVE_CONFIG,
DEFINE_CMD(app_usage, CLIFLAG_PERMISSIVE_CONFIG,
"Display command usage.",
"help|-h|--help","...");
static int commandline_usage(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
static int app_usage(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
{
printf("Serval DNA version %s\nUsage:\n", version_servald);
return cli_usage_parsed(parsed, XPRINTF_STDIO(stdout));
}
@ -50,189 +46,17 @@ There is NO WARRANTY, to the extent permitted by law.\n\
return 0;
}
/* Data structures for accumulating output of a single JNI call.
*/
#ifdef HAVE_JNI_H
#define OUTV_BUFFER_ALLOCSIZE (8192)
jclass IJniResults = NULL;
jmethodID startResultSet, setColumnName, putString, putBlob, putLong, putDouble, totalRowCount;
static int outv_growbuf(struct cli_context *context, size_t needed)
{
assert(context->outv_current <= context->outv_limit);
size_t remaining = (size_t)(context->outv_limit - context->outv_current);
if (remaining < needed) {
size_t cursize = context->outv_current - context->outv_buffer;
size_t newsize = cursize + needed;
// Round up to nearest multiple of OUTV_BUFFER_ALLOCSIZE.
newsize = newsize + OUTV_BUFFER_ALLOCSIZE - ((newsize - 1) % OUTV_BUFFER_ALLOCSIZE + 1);
assert(newsize > cursize);
assert((size_t)(newsize - cursize) >= needed);
context->outv_buffer = realloc(context->outv_buffer, newsize);
if (context->outv_buffer == NULL)
return WHYF("Out of memory allocating %lu bytes", (unsigned long) newsize);
context->outv_current = context->outv_buffer + cursize;
context->outv_limit = context->outv_buffer + newsize;
}
return 0;
}
static int put_blob(struct cli_context *context, jbyte *value, jsize length){
jbyteArray arr = NULL;
if (context->jni_exception)
return -1;
arr = (*context->jni_env)->NewByteArray(context->jni_env, length);
if (arr == NULL || (*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHY("Exception thrown from NewByteArray()");
}
if (value && length>0){
(*context->jni_env)->SetByteArrayRegion(context->jni_env, arr, 0, length, value);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHYF("Exception thrown from SetByteArrayRegion()");
}
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putBlob, arr);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHY("Exception thrown from CallVoidMethod(putBlob)");
}
if (arr)
(*context->jni_env)->DeleteLocalRef(context->jni_env, arr);
return 0;
}
static int outv_end_field(struct cli_context *context)
{
jsize length = context->outv_current - context->outv_buffer;
context->outv_current = context->outv_buffer;
return put_blob(context, (jbyte *)context->outv_buffer, length);
}
int Throw(JNIEnv *env, const char *class, const char *msg)
{
jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, class)) == NULL)
return -1; // exception
(*env)->ThrowNew(env, exceptionClass, msg);
return -1;
}
int initJniTypes(JNIEnv *env)
{
if (IJniResults)
return 0;
IJniResults = (*env)->FindClass(env, "org/servalproject/servaldna/IJniResults");
if (IJniResults==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate class org.servalproject.servaldna.IJniResults");
// make sure the interface class cannot be garbage collected between invocations in the same process
IJniResults = (jclass)(*env)->NewGlobalRef(env, IJniResults);
if (IJniResults==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to create global ref to class org.servalproject.servaldna.IJniResults");
startResultSet = (*env)->GetMethodID(env, IJniResults, "startResultSet", "(I)V");
if (startResultSet==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method startResultSet");
setColumnName = (*env)->GetMethodID(env, IJniResults, "setColumnName", "(ILjava/lang/String;)V");
if (setColumnName==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method setColumnName");
putString = (*env)->GetMethodID(env, IJniResults, "putString", "(Ljava/lang/String;)V");
if (putString==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putString");
putBlob = (*env)->GetMethodID(env, IJniResults, "putBlob", "([B)V");
if (putBlob==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putBlob");
putLong = (*env)->GetMethodID(env, IJniResults, "putLong", "(J)V");
if (putLong==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putLong");
putDouble = (*env)->GetMethodID(env, IJniResults, "putDouble", "(D)V");
if (putDouble==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putDouble");
totalRowCount = (*env)->GetMethodID(env, IJniResults, "totalRowCount", "(I)V");
if (totalRowCount==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method totalRowCount");
return 0;
}
/* JNI entry point to command line. See org.servalproject.servald.ServalD class for the Java side.
JNI method descriptor: "(Ljava/util/List;[Ljava/lang/String;)I"
*/
JNIEXPORT jint JNICALL Java_org_servalproject_servaldna_ServalDCommand_rawCommand(JNIEnv *env, jobject UNUSED(this), jobject outv, jobjectArray args)
{
struct cli_context context;
bzero(&context, sizeof(context));
int r;
// find jni results methods
if ((r=initJniTypes(env))!=0)
return r;
unsigned char status = 0; // to match what the shell gets: 0..255
// Construct argv, argc from this method's arguments.
jsize len = (*env)->GetArrayLength(env, args);
const char *argv[len+1];
bzero(argv, sizeof(argv));
jsize i;
int argc = len;
// From now on, in case of an exception we have to free some resources before
// returning.
static const char *EMPTY="";
for (i = 0; !context.jni_exception && i < len; ++i) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if ((*env)->ExceptionCheck(env)){
context.jni_exception = 1;
}else if (arg == NULL) {
argv[i] = EMPTY;
} else {
argv[i] = (*env)->GetStringUTFChars(env, arg, NULL);
if (argv[i] == NULL)
context.jni_exception = 1;
}
}
if (!context.jni_exception) {
// Set up the output buffer.
context.jniResults = outv;
context.outv_current = context.outv_buffer;
// Execute the command.
context.jni_env = env;
status = parseCommandLine(&context, NULL, argc, argv);
}
// free any temporary output buffer
if (context.outv_buffer)
free(context.outv_buffer);
// Release argv Java string buffers.
for (i = 0; i < len; ++i) {
if (argv[i] && argv[i]!=EMPTY) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if (arg)
(*env)->ReleaseStringUTFChars(env, arg, argv[i]);
}
}
// Deal with Java exceptions: NewStringUTF out of memory in outv_end_field().
if (context.jni_exception || (context.outv_current != context.outv_buffer && outv_end_field(&context) == -1))
return -1;
return (jint) status;
}
#endif /* HAVE_JNI_H */
/* The argc and argv arguments must be passed verbatim from main(argc, argv), so argv[0] is path to
executable.
*/
int parseCommandLine(struct cli_context *context, const char *argv0, int argc, const char *const *args)
/* Parse the command line and load the configuration. If a command was found then execute the
* parsed command and return its return value.
*
* 'context' controls the command output.
*
* 'argv0' must be NULL or be a nul-terminated string containing the name or path of the executable.
* It is used to emit diagnostic messages.
*
* 'argc' and 'args' must contain the command-line words to parse.
*/
int commandline_main(struct cli_context *context, const char *argv0, int argc, const char *const *args)
{
fd_clearstats();
IN();
@ -272,257 +96,17 @@ int parseCommandLine(struct cli_context *context, const char *argv0, int argc, c
return result;
}
/* Write a buffer of data to output. If in a JNI call, then this appends the data to the
current output field, including any embedded nul characters. Returns a non-negative integer on
success, EOF on error.
*/
void cli_write(struct cli_context *UNUSED(context), const unsigned char *buf, size_t len)
int commandline_main_stdio(FILE *output, const char *argv0, int argc, const char *const *args)
{
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
size_t avail = context->outv_limit - context->outv_current;
if (avail < len) {
memcpy(context->outv_current, buf, avail);
context->outv_current = context->outv_limit;
if (outv_growbuf(context, len) == -1)
return;
len -= avail;
buf += avail;
}
memcpy(context->outv_current, buf, len);
context->outv_current += len;
return;
}
#endif
fwrite(buf, len, 1, stdout);
}
struct cli_context_stdio cli_context_stdio = {
.fp = output
};
struct cli_context cli_context = {
.vtable = &cli_vtable_stdio,
.context = &cli_context_stdio
};
/* Write a null-terminated string to output. If in a JNI call, then this appends the string to the
current output field. The terminating null is not included. Returns a non-negative integer on
success, EOF on error.
*/
void cli_puts(struct cli_context *UNUSED(context), const char *str)
{
#ifdef HAVE_JNI_H
if (context && context->jni_env)
cli_write(context, (const unsigned char *) str, strlen(str));
else
#endif
fputs(str, stdout);
}
/* Write a formatted string to output. If in a JNI call, then this appends the string to the
current output field, excluding the terminating null.
*/
void cli_printf(struct cli_context *UNUSED(context), const char *fmt, ...)
{
va_list ap;
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
assert(context->outv_current <= context->outv_limit);
size_t avail = context->outv_limit - context->outv_current;
va_start(ap, fmt);
int count = vsnprintf(context->outv_current, avail, fmt, ap);
va_end(ap);
if (count < 0) {
WHYF("vsnprintf(%p,%zu,%s,...) failed", context->outv_current, avail, alloca_str_toprint(fmt));
return;
} else if ((size_t)count < avail) {
context->outv_current += count;
return;
}
if (outv_growbuf(context, count) == -1)
return;
avail = context->outv_limit - context->outv_current;
va_start(ap, fmt);
count = vsprintf(context->outv_current, fmt, ap);
va_end(ap);
if (count < 0) {
WHYF("vsprintf(%p,%s,...) failed", context->outv_current, alloca_str_toprint(fmt));
return;
}
assert((size_t)count < avail);
context->outv_current += (size_t)count;
} else
#endif
{
va_start(ap, fmt);
if (vfprintf(stdout, fmt, ap) < 0)
WHYF("vfprintf(stdout,%s,...) failed", alloca_str_toprint(fmt));
va_end(ap);
}
}
void cli_columns(struct cli_context *context, int columns, const char *names[])
{
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, startResultSet, columns);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(startResultSet)");
return;
}
int i;
for (i=0;i<columns;i++){
jstring str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, names[i]);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, setColumnName, i, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(setColumnName)");
return;
}
}
return;
}
#endif
cli_printf(context, "%d", columns);
cli_delim(context, "\n");
int i;
for (i=0;i<columns;i++){
cli_puts(context, names[i]);
if (i+1==columns)
cli_delim(context, "\n");
else
cli_delim(context, ":");
}
}
void cli_field_name(struct cli_context *context, const char *name, const char *delim)
{
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
if (context->jni_exception)
return;
jstring str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, name);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, setColumnName, -1, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(setColumnName)");
return;
}
return;
}
#endif
cli_puts(context, name);
cli_delim(context, delim);
}
void cli_put_long(struct cli_context *context, int64_t value, const char *delim){
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putLong, value);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(putLong)");
}
return;
}
#endif
cli_printf(context, "%" PRId64, value);
cli_delim(context, delim);
}
void cli_put_string(struct cli_context *context, const char *value, const char *delim){
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
if (context->jni_exception)
return;
jstring str = NULL;
if (value){
str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, value);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putString, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(putLong)");
}
return;
}
#endif
if (value)
cli_puts(context, value);
cli_delim(context, delim);
}
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim){
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
put_blob(context, (jbyte*)value, length);
return;
}
#endif
if (value)
cli_puts(context, alloca_tohex(value, length));
cli_delim(context, delim);
}
void cli_row_count(struct cli_context *UNUSED(context), int UNUSED(rows)){
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, totalRowCount, rows);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod()");
}
return;
}
#endif
}
/* Delimit the current output field. This closes the current field, so that the next cli_ output
function will start appending to a new field. Returns 0 on success, -1 on error. If not in a
JNI call, then this simply writes a newline to standard output (or the value of the
SERVALD_OUTPUT_DELIMITER env var if set).
*/
void cli_delim(struct cli_context *UNUSED(context), const char *opt)
{
#ifdef HAVE_JNI_H
if (context && context->jni_env) {
outv_end_field(context);
return;
}
#endif
const char *delim = getenv("SERVALD_OUTPUT_DELIMITER");
if (delim == NULL)
delim = opt ? opt : "\n";
fputs(delim, stdout);
}
/* Flush the output fields if they are being written to standard output.
*/
void cli_flush(struct cli_context *UNUSED(context))
{
#ifdef HAVE_JNI_H
if (context && context->jni_env)
return;
#endif
fflush(stdout);
return commandline_main(&cli_context, argv0, argc, args);
}
DEFINE_CMD(app_echo,CLIFLAG_PERMISSIVE_CONFIG,
@ -539,7 +123,7 @@ static int app_echo(const struct cli_parsed *parsed, struct cli_context *context
if (escapes) {
unsigned char buf[strlen(arg)];
size_t len = strn_fromprint(buf, sizeof buf, arg, 0, '\0', NULL);
cli_write(context, buf, len);
cli_write(context, (char *)buf, len);
} else
cli_puts(context, arg);
cli_delim(context, NULL);
@ -562,4 +146,3 @@ static int app_log(const struct cli_parsed *parsed, struct cli_context *UNUSED(c
logMessage(level, __NOWHERE__, "%s", msg);
return 0;
}

View File

@ -1,34 +1,34 @@
/*
Copyright (C) 2014,2015 Serval Project Inc.
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.
*/
Serval DNA command-line parsing
Copyright (C) 2014-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
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.
*/
#ifndef __SERVAL_DNA__COMMANDLINE_H
#define __SERVAL_DNA__COMMANDLINE_H
#include "features.h"
#include <stdio.h> // for FILE
#include "section.h"
#include "cli.h"
#define KEYRING_PIN_OPTION ,"[--keyring-pin=<pin>]"
#define KEYRING_ENTRY_PIN_OPTION ,"[--entry-pin=<pin>]"
#define KEYRING_PIN_OPTIONS KEYRING_PIN_OPTION KEYRING_ENTRY_PIN_OPTION "..."
struct cli_schema;
struct cli_context;
// macros are weird sometimes ....
#define __APPEND_(X,Y) X ## Y
#define _APPEND(X,Y) __APPEND_(X,Y)
@ -46,18 +46,7 @@ DECLARE_SECTION(struct cli_schema, commands);
#define CMD_COUNT (SECTION_START(commands) - SECTION_END(commands))
void cli_flush(struct cli_context *context);
void cli_delim(struct cli_context *context, const char *opt);
void cli_puts(struct cli_context *context, const char *str);
void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( __ATTRIBUTE_format(printf,2,3) ));
void cli_columns(struct cli_context *context, int columns, const char *names[]);
void cli_row_count(struct cli_context *context, int rows);
void cli_field_name(struct cli_context *context, const char *name, const char *delim);
void cli_put_long(struct cli_context *context, int64_t value, const char *delim);
void cli_put_string(struct cli_context *context, const char *value, const char *delim);
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim);
void cli_write(struct cli_context *context, const unsigned char *buf, size_t len);
int commandline_main(struct cli_context *context, const char *argv0, int argc, const char *const *args);
int commandline_main_stdio(FILE *output, const char *argv0, int argc, const char *const *args);
void cli_cleanup();
#endif
#endif // __SERVAL_DNA__COMMANDLINE_H

17
conf.c
View File

@ -35,7 +35,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
struct cf_om_node *cf_om_root = NULL;
static __thread struct file_meta conffile_meta = FILE_META_UNKNOWN;
int cf_limbo = 1;
__thread int cf_initialised = 0;
__thread int cf_limbo = 1;
__thread struct config_main config;
static __thread struct file_meta config_meta = FILE_META_UNKNOWN;
@ -160,11 +161,14 @@ int cf_om_save()
int cf_init()
{
cf_limbo = 1;
conffile_meta = config_meta = FILE_META_UNKNOWN;
memset(&config, 0, sizeof config);
if (cf_dfl_config_main(&config) == CFERROR)
return -1;
if (!cf_initialised) {
memset(&config, 0, sizeof config);
if (cf_dfl_config_main(&config) == CFERROR)
return -1;
conffile_meta = config_meta = FILE_META_UNKNOWN;
cf_limbo = 1;
cf_initialised = 1;
}
return 0;
}
@ -184,6 +188,7 @@ static int reload_and_parse(int permissive, int strict)
{
int result = CFOK;
int changed = 0;
assert(cf_initialised);
if (cf_limbo)
result = cf_dfl_config_main(&config);
if (result == CFOK || result == CFEMPTY) {

3
conf.h
View File

@ -699,7 +699,8 @@ int cf_fmt_socket_type(const char **, const short *typep);
int cf_opt_encapsulation(short *encapp, const char *text);
int cf_fmt_encapsulation(const char **, const short *encapp);
extern int cf_limbo;
extern __thread int cf_initialised;
extern __thread int cf_limbo;
extern __thread struct config_main config;
#define IF_DEBUG(flagname) (config.debug.flagname)

View File

@ -236,6 +236,7 @@ ATOM(bool_t, http_server, 0, boolean,, "")
ATOM(bool_t, httpd, 0, boolean,, "")
ATOM(bool_t, nohttptx, 0, boolean,, "")
ATOM(bool_t, io, 0, boolean,, "")
ATOM(bool_t, jni, 0, boolean,, "")
ATOM(bool_t, verbose_io, 0, boolean,, "")
ATOM(bool_t, keyring, 0, boolean,, "")
ATOM(bool_t, mdprequests, 0, boolean,, "")

View File

@ -32,10 +32,10 @@ static int httpd_dispatch(struct http_request *hr)
INFOF("HTTP SERVER, %s %s", r->http.verb, r->http.path);
r->http.response.content_generator = NULL;
struct http_handler *handler, *parser=NULL;
struct http_handler *parser=NULL;
const char *remainder=NULL;
struct http_handler *handler;
size_t match_len=0;
for (handler = SECTION_START(httpd); handler < SECTION_END(httpd); ++handler) {
size_t path_len = strlen(handler->path);
if (parser && path_len < match_len)

View File

@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 The Serval Project
* Copyright (C) 2016 Flinders University
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
@ -113,7 +114,11 @@ public abstract class AbstractId {
buff.put(this.binary);
}
public String toHex(int offset, int len) {
public static String toHex(byte[] binary) {
return toHex(binary, 0, binary.length);
}
public static String toHex(byte[] binary, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i = offset; i < offset + len && i < binary.length; i++) {
sb.append(Character.forDigit(((binary[i]) & 0xf0) >> 4, 16));
@ -123,15 +128,15 @@ public abstract class AbstractId {
}
public String toHex(int len) {
return toHex(0, len);
return toHex(binary, 0, len);
}
public String toHex() {
return toHex(0, binary.length);
return toHex(binary, 0, binary.length);
}
public String abbreviation() {
return toHex(0, 4);
return toHex(binary, 0, 4);
}

View File

@ -2,24 +2,11 @@ package org.servalproject.servaldna;
public abstract class AbstractJniResults implements IJniResults {
@Override
public void startResultSet(int columns) {
putBlob(Integer.toString(columns).getBytes());
}
@Override
public void setColumnName(int i, String name) {
putBlob(name.getBytes());
}
@Override
public void putString(String value) {
putBlob((value != null) ? value.getBytes() : null);
}
@Override
public abstract void putBlob(byte[] value);
@Override
public void putLong(long value) {
putBlob(Long.toString(value).getBytes());
@ -31,7 +18,24 @@ public abstract class AbstractJniResults implements IJniResults {
}
@Override
public void totalRowCount(int rows) {
public void putHexValue(byte[] value) {
putBlob(value);
}
@Override
public abstract void putBlob(byte[] blob);
@Override
public void startTable(int column_count) {
putBlob(Integer.toString(column_count).getBytes());
}
@Override
public void setColumnName(int i, String name) {
putBlob(name.getBytes());
}
@Override
public void endTable(int row_count) {
}
}

View File

@ -1,11 +1,33 @@
/**
* Copyright (C) 2014-2015 Serval Project Inc.
* Copyright (C) 2016 Flinders University
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software 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 3 of the License, or
* (at your option) any later version.
*
* This source code 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 source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna;
public interface IJniResults {
public void startResultSet(int columns);
public void setColumnName(int column, String name);
public void putString(String value);
public void putBlob(byte[] value);
public void putLong(long value);
public void putDouble(double value);
public void totalRowCount(int rows);
}
public void putHexValue(byte[] value); // short binary blob like SID, BID, crypto keys
public void putBlob(byte[] blob); // large binary blob like a manifest or payload
public void startTable(int column_count);
public void setColumnName(int column, String name);
public void endTable(int row_count);
}

View File

@ -1,16 +1,35 @@
/**
* Copyright (C) 2014 Serval Project Inc.
* Copyright (C) 2016 Flinders University
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software 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 3 of the License, or
* (at your option) any later version.
*
* This source code 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 source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna;
/**
* Created by jeremy on 18/02/14.
*/
public class JniResult implements IJniResults{
protected String columnName=null;
protected String command[];
protected int result;
void setCommand(String command[]){
void setCommand(String command[]) {
this.command = command;
}
void setResult(int result) throws ServalDFailureException {
this.result = result;
if (result == ServalDCommand.STATUS_ERROR)
@ -22,7 +41,27 @@ public class JniResult implements IJniResults{
}
@Override
public void startResultSet(int columns) {
public void putString(String value) {
}
@Override
public void putLong(long value) {
}
@Override
public void putDouble(double value) {
}
@Override
public void putHexValue(byte[] value) {
}
@Override
public void putBlob(byte[] blob) {
}
@Override
public void startTable(int column_count) {
}
@Override
@ -31,26 +70,6 @@ public class JniResult implements IJniResults{
}
@Override
public void putString(String value) {
}
@Override
public void putBlob(byte[] value) {
}
@Override
public void putLong(long value) {
}
@Override
public void putDouble(double value) {
}
@Override
public void totalRowCount(int rows) {
public void endTable(int row_count) {
}
}

View File

@ -1,12 +1,30 @@
/**
* Copyright (C) 2014-2015 Serval Project Inc.
* Copyright (C) 2016 Flinders University
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software 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 3 of the License, or
* (at your option) any later version.
*
* This source code 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 source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna;
/**
* Created by jeremy on 18/02/14.
*/
public abstract class JniResultList<T extends JniResult> implements IJniResults {
private String names[];
private int column =-1;
private int columns = -1;
private int column_count = -1;
private T currentRow;
private AsyncResult<T> results;
@ -16,9 +34,9 @@ public abstract class JniResultList<T extends JniResult> implements IJniResults
public abstract T create();
@Override
public void startResultSet(int columns) {
names = new String[columns];
this.columns = columns;
public void startTable(int column_count) {
names = new String[column_count];
this.column_count = column_count;
}
@Override
@ -26,14 +44,19 @@ public abstract class JniResultList<T extends JniResult> implements IJniResults
names[column]=name;
}
@Override
public void endTable(int row_count) {
}
private void prepareCol(){
column++;
if (column==0)
currentRow = create();
currentRow.columnName = names[column];
}
private void endCol(){
if (column+1>=columns){
if (column+1>=column_count){
if (currentRow!=null)
results.result(currentRow);
currentRow=null;
@ -48,13 +71,6 @@ public abstract class JniResultList<T extends JniResult> implements IJniResults
endCol();
}
@Override
public void putBlob(byte[] value) {
prepareCol();
currentRow.putBlob(value);
endCol();
}
@Override
public void putLong(long value) {
prepareCol();
@ -70,6 +86,16 @@ public abstract class JniResultList<T extends JniResult> implements IJniResults
}
@Override
public void totalRowCount(int rows) {
public void putBlob(byte[] blob) {
prepareCol();
currentRow.putBlob(blob);
endCol();
}
@Override
public void putHexValue(byte[] value) {
prepareCol();
currentRow.putBlob(value);
endCol();
}
}

View File

@ -1,5 +1,6 @@
/**
* Copyright (C) 2011 The Serval Project
* Copyright (C) 2014-2015 Serval Project Inc.
* Copyright (C) 2016 Flinders University
*
* This file is part of Serval Software (http://www.servalproject.org)
*
@ -34,7 +35,7 @@ public class ServalDCommand
static
{
System.loadLibrary("serval");
System.loadLibrary("servald");
}
public static final int STATUS_ERROR = 255;
@ -297,9 +298,9 @@ public class ServalDCommand
public void putString(String value) {
if (this.columnName.equals("did"))
this.did = value;
if (this.columnName.equals("name"))
else if (this.columnName.equals("name"))
this.name = value;
if (this.columnName.equals("uri"))
else if (this.columnName.equals("uri"))
this.uri = value;
}

View File

@ -1,132 +1,116 @@
/**
* Copyright (C) 2016 Flinders University
* Copyright (C) 2014-2015 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software 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 3 of the License, or
* (at your option) any later version.
*
* This source code 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 source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.test;
import org.servalproject.servaldna.AsyncResult;
import org.servalproject.servaldna.ChannelSelector;
import org.servalproject.servaldna.IJniServer;
import org.servalproject.servaldna.MdpDnaLookup;
import org.servalproject.servaldna.MdpServiceLookup;
import org.servalproject.servaldna.ResultList;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDFailureException;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.AbstractId;
import org.servalproject.servaldna.IJniResults;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Arrays;
/**
* Created by jeremy on 20/02/14.
*/
public class CommandLine {
static void log(String msg){
System.err.println(new Date().toString()+" "+msg);
}
public static void command(String... args) throws ServalDFailureException {
ServalDCommand.command(new IJniResults() {
int column_count = -1;
int column = 0;
static void getPeers() throws ServalDFailureException {
List<ServalDCommand.IdentityResult> peers = new LinkedList<ServalDCommand.IdentityResult>();
ServalDCommand.idPeers(new ResultList<ServalDCommand.IdentityResult>(peers));
for(ServalDCommand.IdentityResult i:peers){
ServalDCommand.IdentityResult details = ServalDCommand.reverseLookup(i.subscriberId);
System.out.println(details.getResult()==0?details.toString():i.toString());
}
}
static void lookup(String did) throws IOException, InterruptedException, ServalDInterfaceException {
MdpDnaLookup lookup = new ServerControl().getMdpDnaLookup(new ChannelSelector(), new AsyncResult<ServalDCommand.LookupResult>() {
@Override
public void result(ServalDCommand.LookupResult nextResult) {
System.out.println(nextResult.toString());
private void eol() {
if (column_count == -1 || ++column >= column_count) {
System.out.println("");
column = 0;
}
else
System.out.print(":");
}
});
lookup.sendRequest(SubscriberId.broadcastSid, did);
Thread.sleep(3000);
lookup.close();
}
static void service(String pattern) throws IOException, InterruptedException, ServalDInterfaceException {
MdpServiceLookup lookup = new ServerControl().getMdpServiceLookup(new ChannelSelector(), new AsyncResult<MdpServiceLookup.ServiceResult>() {
@Override
public void result(MdpServiceLookup.ServiceResult nextResult) {
System.out.println(nextResult.toString());
public void putString(String value) {
System.out.print(value);
eol();
}
});
lookup.sendRequest(SubscriberId.broadcastSid, pattern);
Thread.sleep(3000);
lookup.close();
@Override
public void putLong(long value) {
System.out.print(value);
eol();
}
@Override
public void putDouble(double value) {
System.out.print(value);
eol();
}
@Override
public void putBlob(byte[] blob) {
System.out.print(new String(blob));
eol();
}
@Override
public void putHexValue(byte[] blob) {
System.out.print(AbstractId.toHex(blob));
eol();
}
@Override
public void startTable(int column_count) {
this.column_count = column_count;
System.out.println(column_count);
}
@Override
public void setColumnName(int column, String name) {
System.out.print(name);
if (column + 1 >= column_count)
System.out.println("");
else
System.out.print(":");
}
@Override
public void endTable(int rows) {
this.column_count = -1;
}
}, args);
}
private static Runnable server = new Runnable() {
@Override
public void run() {
ServalDCommand.server(new IJniServer() {
@Override
public long aboutToWait(long now, long nextRun, long nextWake) {
return nextWake;
}
@Override
public void wokeUp() {
}
@Override
public void started(String instancePath, int pid, int mdpPort, int httpPort) {
System.err.println("Started instance " + instancePath);
synchronized (server) {
server.notifyAll();
}
}
}, "", new String[]{""});
}
};
private static class ServerStopped extends RuntimeException{}
private static void server() throws InterruptedException, ServalDFailureException {
System.err.println("Starting server thread");
Thread serverThread = new Thread(server, "server");
serverThread.start();
System.err.println("Waiting for server to start");
synchronized (server) {
server.wait();
}
Thread.sleep(500);
ServalDCommand.configSync();
Thread.sleep(500);
// Note, we don't really support stopping the server cleanly from within the JVM.
System.exit(0);
}
public static void main(String... args){
if (args.length<1)
return;
public static void main(String... args) {
try {
String methodName = args[0];
Object result=null;
if (methodName.equals("server"))
server();
if (methodName.equals("start"))
result=ServalDCommand.serverStart();
if (methodName.equals("stop"))
result=ServalDCommand.serverStop();
if (methodName.equals("peers"))
getPeers();
if (methodName.equals("lookup"))
lookup(args.length >= 2 ? args[1] : "");
if (methodName.equals("service"))
service(args.length >= 2 ? args[1] : "");
for (int i = 0; i != args.length; ++i)
if ("(null)".equals(args[i]))
args[i] = null;
if (result!=null)
System.err.println(result.toString());
int repeatCount = 1;
if (args[0].equals("--repeat")) {
repeatCount = Integer.decode(args[1]);
args = Arrays.copyOfRange(args, 2, args.length);
}
while (repeatCount-- > 0) {
command(args);
}
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -1,86 +1,161 @@
/**
* Copyright (C) 2016 Flinders University
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software 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 3 of the License, or
* (at your option) any later version.
*
* This source code 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 source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.test;
import org.servalproject.servaldna.IJniResults;
import org.servalproject.servaldna.AsyncResult;
import org.servalproject.servaldna.ChannelSelector;
import org.servalproject.servaldna.IJniServer;
import org.servalproject.servaldna.MdpDnaLookup;
import org.servalproject.servaldna.MdpServiceLookup;
import org.servalproject.servaldna.ResultList;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDFailureException;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.SubscriberId;
import java.util.Arrays;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
class ServalDTests
{
public static int printCommand(final String fieldDelim, final String rowDelim, String... args) throws ServalDFailureException {
return ServalDCommand.command(new IJniResults() {
int columns = -1;
int column = -1;
static void log(String msg) {
System.err.println(new Date().toString()+" "+msg);
}
static void getPeers() throws ServalDFailureException {
List<ServalDCommand.IdentityResult> peers = new LinkedList<ServalDCommand.IdentityResult>();
ServalDCommand.idPeers(new ResultList<ServalDCommand.IdentityResult>(peers));
for(ServalDCommand.IdentityResult i:peers){
ServalDCommand.IdentityResult details = ServalDCommand.reverseLookup(i.subscriberId);
System.out.println(details.getResult()==0?details.toString():i.toString());
}
}
static void lookup(String did) throws IOException, InterruptedException, ServalDInterfaceException {
MdpDnaLookup lookup = new ServerControl().getMdpDnaLookup(new ChannelSelector(), new AsyncResult<ServalDCommand.LookupResult>() {
@Override
public void startResultSet(int columns) {
this.columns = columns;
public void result(ServalDCommand.LookupResult nextResult) {
System.out.println(nextResult.toString());
}
});
lookup.sendRequest(SubscriberId.broadcastSid, did);
Thread.sleep(3000);
lookup.close();
}
static void service(String pattern) throws IOException, InterruptedException, ServalDInterfaceException {
MdpServiceLookup lookup = new ServerControl().getMdpServiceLookup(new ChannelSelector(), new AsyncResult<MdpServiceLookup.ServiceResult>() {
@Override
public void setColumnName(int column, String name) {
System.out.print(name + fieldDelim);
if (column >= 0 && column + 1 == columns)
System.out.println();
public void result(MdpServiceLookup.ServiceResult nextResult) {
System.out.println(nextResult.toString());
}
});
lookup.sendRequest(SubscriberId.broadcastSid, pattern);
Thread.sleep(3000);
lookup.close();
}
private void eol() {
if (columns == -1 || ++column == columns) {
System.out.print(rowDelim);
column = -1;
private static class ServerRunnable implements Runnable {
public boolean running = false;
@Override
public void run() {
ServalDCommand.server(new IJniServer() {
@Override
public long aboutToWait(long now, long nextRun, long nextWake) {
return nextWake;
}
}
@Override
public void putString(String value) {
System.out.print(value);
eol();
}
@Override
public void wokeUp() {
}
@Override
public void putBlob(byte[] value) {
System.out.print(new String(value));
eol();
}
@Override
public void started(String instancePath, int pid, int mdpPort, int httpPort) {
System.out.println("Started server pid=" + pid + " instance=" + instancePath);
synchronized (server) {
running = true;
server.notifyAll();
}
}
}, "", new String[]{""});
@Override
public void putLong(long value) {
System.out.print(value);
eol();
synchronized (server) {
running = false;
server.notifyAll();
}
}
}
@Override
public void putDouble(double value) {
System.out.print(value);
eol();
}
private static ServerRunnable server = new ServerRunnable();
@Override
public void totalRowCount(int rows) {
private static class ServerStopped extends RuntimeException{}
private static void server() throws InterruptedException, ServalDFailureException {
System.out.println("Starting server thread");
Thread serverThread = new Thread(server, "server");
serverThread.start();
synchronized (server) {
while (!server.running) {
System.out.println("Waiting for server to start");
server.wait();
}
}, args);
}
synchronized (server) {
while (server.running) {
System.out.println("Waiting for server to stop");
server.wait();
}
}
}
public static void main(String... args)
{
try {
for (int i = 0; i != args.length; ++i)
if ("(null)".equals(args[i]))
args[i] = null;
String methodName = args[0];
Object result = null;
if (methodName.equals("server"))
server();
else if (methodName.equals("start"))
result=ServalDCommand.serverStart();
else if (methodName.equals("stop"))
result=ServalDCommand.serverStop();
else if (methodName.equals("peers"))
getPeers();
else if (methodName.equals("lookup"))
lookup(args.length >= 2 ? args[1] : "");
else if (methodName.equals("service"))
service(args.length >= 2 ? args[1] : "");
else
throw new Exception("unknown command: " + methodName);
int repeatCount=1;
if (args[0].equals("repeat")){
repeatCount = Integer.decode(args[1]);
args = Arrays.copyOfRange(args, 2, args.length);
}
while(repeatCount>0){
printCommand("", " ", args);
System.out.println();
repeatCount--;
}
if (result != null)
System.err.println(result.toString());
}
catch (Exception e) {
e.printStackTrace();

459
jni_commandline.c Normal file
View File

@ -0,0 +1,459 @@
/*
Serval DNA JNI command-line entry points
Copyright (C) 2016 Flinders University
Copyright (C) 2010-2013 Serval Project Inc.
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.
*/
#include <assert.h>
#include "jni_common.h"
#include "commandline.h"
#include "mem.h"
#include "os.h"
#include "str.h"
#include "strbuf_helpers.h"
#include "conf.h"
#include "debug.h"
struct cli_vtable cli_vtable_jni;
struct jni_context {
JNIEnv *jni_env;
int jni_exception;
jobject jniResults;
char *outv_buffer;
char *outv_current;
char *outv_limit;
};
/* Data structures for accumulating output of a single JNI call.
*/
#define OUTV_BUFFER_ALLOCSIZE (8192)
jclass IJniResults = NULL;
jmethodID putString;
jmethodID putLong;
jmethodID putDouble;
jmethodID putHexValue;
jmethodID putBlob;
jmethodID startTable;
jmethodID setColumnName;
jmethodID endTable;
static int outv_growbuf(struct jni_context *context, size_t needed)
{
assert(context->outv_current <= context->outv_limit);
size_t remaining = (size_t)(context->outv_limit - context->outv_current);
if (remaining < needed) {
size_t cursize = context->outv_current - context->outv_buffer;
size_t newsize = cursize + needed;
// Round up to nearest multiple of OUTV_BUFFER_ALLOCSIZE.
newsize = newsize + OUTV_BUFFER_ALLOCSIZE - ((newsize - 1) % OUTV_BUFFER_ALLOCSIZE + 1);
assert(newsize > cursize);
assert((size_t)(newsize - cursize) >= needed);
if ((context->outv_buffer = erealloc(context->outv_buffer, newsize)) == NULL)
return -1;
context->outv_current = context->outv_buffer + cursize;
context->outv_limit = context->outv_buffer + newsize;
}
return 0;
}
static void outv_write(struct jni_context *context, const char *buf, size_t len)
{
// Converts NUL chars to Modified UTF-8 NUL (c0 80) so that Java's UTF String will treat it as an
// embedded NUL.
size_t utflen = len;
const char *s;
for (s = buf; s != buf + len; ++s)
if (!*s)
++utflen;
if (outv_growbuf(context, utflen) == -1)
return;
for (s = buf; s != buf + len; ++s)
if (*s)
*context->outv_current++ = *s;
else {
*context->outv_current++ = '\xc0';
*context->outv_current++ = '\x80';
}
assert(context->outv_current <= context->outv_limit);
}
static int put_string(struct jni_context *context, const char *str)
{
if (context->jni_exception)
return -1;
jstring jstr = NULL;
if (str) {
jstr = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, str);
if (jstr == NULL) {
context->jni_exception = 1;
return WHY("Exception thrown from NewStringUTF()");
}
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putString, jstr);
(*context->jni_env)->DeleteLocalRef(context->jni_env, jstr);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHY("Exception thrown from CallVoidMethod(putLong)");
}
return 0;
}
static int put_byte_array(struct jni_context *context, jbyte *blob, jsize length, jmethodID method, const char *method_name)
{
jbyteArray arr = NULL;
if (context->jni_exception)
return -1;
arr = (*context->jni_env)->NewByteArray(context->jni_env, length);
if (arr == NULL || (*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHY("Exception thrown from NewByteArray()");
}
if (blob && length) {
(*context->jni_env)->SetByteArrayRegion(context->jni_env, arr, 0, length, blob);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHY("Exception thrown from SetByteArrayRegion()");
}
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, method, arr);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
return WHYF("Exception thrown from CallVoidMethod(%s)", method_name);
}
if (arr)
(*context->jni_env)->DeleteLocalRef(context->jni_env, arr);
return 0;
}
static int outv_end_field(struct jni_context *context)
{
// append terminating nul
if (outv_growbuf(context, 1) == -1)
return -1;
*context->outv_current++ = '\0';
context->outv_current = context->outv_buffer;
return put_string(context, context->outv_buffer);
}
static int initJniTypes(JNIEnv *env)
{
if (IJniResults)
return 0;
cf_init();
IJniResults = (*env)->FindClass(env, "org/servalproject/servaldna/IJniResults");
if (IJniResults==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate class org.servalproject.servaldna.IJniResults");
// make sure the interface class cannot be garbage collected between invocations in the same process
IJniResults = (jclass)(*env)->NewGlobalRef(env, IJniResults);
if (IJniResults==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to create global ref to class org.servalproject.servaldna.IJniResults");
putString = (*env)->GetMethodID(env, IJniResults, "putString", "(Ljava/lang/String;)V");
if (putString==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method putString");
putLong = (*env)->GetMethodID(env, IJniResults, "putLong", "(J)V");
if (putLong==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method putLong");
putDouble = (*env)->GetMethodID(env, IJniResults, "putDouble", "(D)V");
if (putDouble==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method putDouble");
putHexValue = (*env)->GetMethodID(env, IJniResults, "putHexValue", "([B)V");
if (putHexValue==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method putHexValue");
putBlob = (*env)->GetMethodID(env, IJniResults, "putBlob", "([B)V");
if (putBlob==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method putBlob");
startTable = (*env)->GetMethodID(env, IJniResults, "startTable", "(I)V");
if (startTable==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method startTable");
setColumnName = (*env)->GetMethodID(env, IJniResults, "setColumnName", "(ILjava/lang/String;)V");
if (setColumnName==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method setColumnName");
endTable = (*env)->GetMethodID(env, IJniResults, "endTable", "(I)V");
if (endTable==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method endTable");
return 0;
}
/* JNI entry point to command line. See org.servalproject.servald.ServalD class for the Java side.
JNI method descriptor: "(Ljava/util/List;[Ljava/lang/String;)I"
*/
JNIEXPORT jint JNICALL Java_org_servalproject_servaldna_ServalDCommand_rawCommand(JNIEnv *env, jobject UNUSED(this), jobject outv, jobjectArray args)
{
int r;
// find jni results methods
if ((r=initJniTypes(env))!=0)
return r;
unsigned char status = 0; // to match what the shell gets: 0..255
// Construct argv, argc from this method's arguments.
jsize len = (*env)->GetArrayLength(env, args);
const char *argv[len+1];
bzero(argv, sizeof(argv));
// From now on, in case of an exception we have to free some resources before
// returning.
static const char *EMPTY="";
struct jni_context context;
bzero(&context, sizeof(context));
struct cli_context cli_context = {
.vtable = &cli_vtable_jni,
.context = &context
};
jsize i;
for (i = 0; !context.jni_exception && i < len; ++i) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if ((*env)->ExceptionCheck(env)){
context.jni_exception = 1;
}else if (arg == NULL) {
argv[i] = EMPTY;
} else {
argv[i] = (*env)->GetStringUTFChars(env, arg, NULL);
if (argv[i] == NULL)
context.jni_exception = 1;
}
}
if (!context.jni_exception) {
// Set up the output buffer.
context.jniResults = outv;
context.outv_current = context.outv_buffer;
// Execute the command.
context.jni_env = env;
status = commandline_main(&cli_context, NULL, (int)len, argv);
}
// free any temporary output buffer
if (context.outv_buffer)
free(context.outv_buffer);
// Release argv Java string buffers.
for (i = 0; i < len; ++i) {
if (argv[i] && argv[i]!=EMPTY) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if (arg)
(*env)->ReleaseStringUTFChars(env, arg, argv[i]);
}
}
// Deal with Java exceptions: NewStringUTF out of memory in outv_end_field().
if (context.jni_exception || (context.outv_current != context.outv_buffer && outv_end_field(&context) == -1))
return -1;
return (jint) status;
}
/* Output primitives for JNI.
*/
static struct jni_context *jni_context(struct cli_context *cli_context)
{
return (struct jni_context *)(cli_context->context);
}
static void jni_delim(struct cli_context *cli_context, const char *UNUSED(opt))
{
DEBUGF(jni, "");
outv_end_field(jni_context(cli_context));
}
static void jni_write(struct cli_context *cli_context, const char *buf, size_t len)
{
DEBUGF(jni, "%s", alloca_toprint(-1, buf, len));
outv_write(jni_context(cli_context), buf, len);
}
static void jni_puts(struct cli_context *cli_context, const char *str)
{
DEBUGF(jni, "%s", alloca_str_toprint(str));
outv_write(jni_context(cli_context), str, strlen(str));
}
static void jni_vprintf(struct cli_context *cli_context, const char *fmt, va_list ap)
{
DEBUGF(jni, "%s, ...", alloca_str_toprint(fmt));
struct jni_context *context = jni_context(cli_context);
assert(context->outv_current <= context->outv_limit);
size_t avail = context->outv_limit - context->outv_current;
va_list aq;
va_copy(aq, ap);
int count = vsnprintf(context->outv_current, avail, fmt, aq);
va_end(aq);
if (count < 0) {
WHYF("vsnprintf(%p,%zu,%s,...) failed", context->outv_current, avail, alloca_str_toprint(fmt));
return;
} else if ((size_t)count < avail) {
context->outv_current += count;
return;
}
if (outv_growbuf(context, count) == -1)
return;
avail = context->outv_limit - context->outv_current;
va_copy(aq, ap);
count = vsprintf(context->outv_current, fmt, aq);
va_end(aq);
if (count < 0) {
WHYF("vsprintf(%p,%s,...) failed", context->outv_current, alloca_str_toprint(fmt));
return;
}
assert((size_t)count < avail);
context->outv_current += (size_t)count;
}
static void jni_put_long(struct cli_context *cli_context, int64_t value, const char *UNUSED(delim_opt))
{
DEBUGF(jni, "%" PRId64, value);
struct jni_context *context = jni_context(cli_context);
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putLong, value);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(putLong)");
}
}
static void jni_put_string(struct cli_context *cli_context, const char *value, const char *UNUSED(delim_opt))
{
DEBUGF(jni, "%s", alloca_str_toprint(value));
struct jni_context *context = jni_context(cli_context);
if (context->jni_exception)
return;
jstring str = NULL;
if (value){
str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, value);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, putString, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(putLong)");
}
}
static void jni_put_hexvalue(struct cli_context *cli_context, const unsigned char *value, size_t length, const char *UNUSED(delim_opt))
{
DEBUGF(jni, "%s", alloca_tohex(value, length));
struct jni_context *context = jni_context(cli_context);
put_byte_array(context, (jbyte*)value, length, putHexValue, "putHexValue");
}
static void jni_put_blob(struct cli_context *cli_context, const unsigned char *blob, size_t length, const char *UNUSED(delim_opt))
{
DEBUGF(jni, "%s", alloca_tohex(blob, length));
struct jni_context *context = jni_context(cli_context);
put_byte_array(context, (jbyte*)blob, length, putBlob, "putBlob");
}
static void jni_start_table(struct cli_context *cli_context, size_t column_count, const char *column_names[])
{
DEBUGF(jni, "%s", alloca_argv(column_count, column_names));
struct jni_context *context = jni_context(cli_context);
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, startTable, (jint)column_count);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(startTable)");
return;
}
size_t i;
for (i = 0; i != column_count; ++i) {
jstring str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, column_names[i]);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, setColumnName, i, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(setColumnName)");
return;
}
}
}
static void jni_end_table(struct cli_context *cli_context, size_t row_count)
{
DEBUGF(jni, "%zu", row_count);
struct jni_context *context = jni_context(cli_context);
if (context->jni_exception)
return;
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, endTable, (jint)row_count);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod()");
}
}
static void jni_field_name(struct cli_context *cli_context, const char *name, const char *UNUSED(delim_opt))
{
DEBUGF(jni, "%s", name);
struct jni_context *context = jni_context(cli_context);
if (context->jni_exception)
return;
jstring str = (jstring)(*context->jni_env)->NewStringUTF(context->jni_env, name);
if (str == NULL) {
context->jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*context->jni_env)->CallVoidMethod(context->jni_env, context->jniResults, setColumnName, -1, str);
(*context->jni_env)->DeleteLocalRef(context->jni_env, str);
if ((*context->jni_env)->ExceptionCheck(context->jni_env)) {
context->jni_exception = 1;
WHY("Exception thrown from CallVoidMethod(setColumnName)");
return;
}
}
static void jni_flush(struct cli_context *UNUSED(cli_context))
{
DEBUGF(jni, "");
// nop
}
struct cli_vtable cli_vtable_jni = {
.delim = jni_delim,
.write = jni_write,
.puts = jni_puts,
.vprintf = jni_vprintf,
.put_long = jni_put_long,
.put_string = jni_put_string,
.put_hexvalue = jni_put_hexvalue,
.put_blob = jni_put_blob,
.start_table = jni_start_table,
.end_table = jni_end_table,
.field_name = jni_field_name,
.flush = jni_flush
};

31
jni_common.c Normal file
View File

@ -0,0 +1,31 @@
/*
Serval DNA JNI common definitions
Copyright (C) 2016 Flinders University
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.
*/
#include "jni_common.h"
#include "log.h"
int jni_throw(JNIEnv *env, const char *class_name, const char *msg)
{
jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, class_name)) == NULL) {
return WHYF("Java exception class not found: %s", class_name);
}
(*env)->ThrowNew(env, exceptionClass, msg);
return -1;
}

34
jni_common.h Normal file
View File

@ -0,0 +1,34 @@
/*
Serval DNA JNI common definitions
Copyright (C) 2016 Flinders University
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.
*/
#ifndef HAVE_JNI_H
#error <jni.h> is not available
#endif
#include <jni.h>
// Stop OpenJDK 7 from foisting their UNUSED() macro on us in <jni_md.h>
// N.B. This means that "feature.h" can only be included _after_ this header
// file, because it defines UNUSED().
#ifdef UNUSED
# undef UNUSED
#endif
// Throw a Java exception and return -1.
int jni_throw(JNIEnv *env, const char *class_name, const char *msg);

157
jni_server.c Normal file
View File

@ -0,0 +1,157 @@
/*
Serval DNA server main loop - JNI entry point
Copyright (C) 2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
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.
*/
#include <assert.h>
#include "jni_common.h"
#include "server.h"
#include "serval.h"
#include "conf.h"
#include "instance.h"
JNIEnv *server_env=NULL;
jclass IJniServer= NULL;
jmethodID aboutToWait, wokeUp, started;
jobject JniCallback;
static time_ms_t waiting(time_ms_t now, time_ms_t next_run, time_ms_t next_wakeup)
{
if (server_env && JniCallback){
jlong r = (*server_env)->CallLongMethod(server_env, JniCallback, aboutToWait, (jlong)now, (jlong)next_run, (jlong)next_wakeup);
// stop the server if there are any issues
if ((*server_env)->ExceptionCheck(server_env)){
serverMode=SERVER_CLOSING;
INFO("Stopping server due to exception");
return now;
}
return r;
}
return next_wakeup;
}
static void wokeup()
{
if (server_env && JniCallback){
(*server_env)->CallVoidMethod(server_env, JniCallback, wokeUp);
// stop the server if there are any issues
if ((*server_env)->ExceptionCheck(server_env)){
INFO("Stopping server due to exception");
serverMode=SERVER_CLOSING;
}
}
}
JNIEXPORT jint JNICALL Java_org_servalproject_servaldna_ServalDCommand_server(
JNIEnv *env, jobject UNUSED(this), jobject callback, jobject keyring_pin, jobjectArray entry_pins)
{
if (!IJniServer){
cf_init();
IJniServer = (*env)->FindClass(env, "org/servalproject/servaldna/IJniServer");
if (IJniServer==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate class org.servalproject.servaldna.IJniServer");
// make sure the interface class cannot be garbage collected between invocations
IJniServer = (jclass)(*env)->NewGlobalRef(env, IJniServer);
if (IJniServer==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to create global ref to class org.servalproject.servaldna.IJniServer");
aboutToWait = (*env)->GetMethodID(env, IJniServer, "aboutToWait", "(JJJ)J");
if (aboutToWait==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method aboutToWait");
wokeUp = (*env)->GetMethodID(env, IJniServer, "wokeUp", "()V");
if (wokeUp==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method wokeUp");
started = (*env)->GetMethodID(env, IJniServer, "started", "(Ljava/lang/String;III)V");
if (started==NULL)
return jni_throw(env, "java/lang/IllegalStateException", "Unable to locate method started");
}
int pid = server_pid();
if (pid < 0)
return jni_throw(env, "java/lang/IllegalStateException", "Failed to read server pid");
if (pid>0)
return jni_throw(env, "java/lang/IllegalStateException", "Server already running");
cf_reload_strict();
int ret = -1;
{
const char *cpin = keyring_pin?(*env)->GetStringUTFChars(env, keyring_pin, NULL):NULL;
if (cpin != NULL){
keyring = keyring_open_instance(cpin);
(*env)->ReleaseStringUTFChars(env, keyring_pin, cpin);
}else{
keyring = keyring_open_instance("");
}
}
// Always open all PIN-less entries.
keyring_enter_pin(keyring, "");
if (entry_pins){
jsize len = (*env)->GetArrayLength(env, entry_pins);
jsize i;
for (i = 0; i < len; ++i) {
const jstring pin = (jstring)(*env)->GetObjectArrayElement(env, entry_pins, i);
if ((*env)->ExceptionCheck(env))
goto end;
const char *cpin = (*env)->GetStringUTFChars(env, pin, NULL);
if (cpin != NULL){
keyring_enter_pin(keyring, cpin);
(*env)->ReleaseStringUTFChars(env, pin, cpin);
}
}
}
if (server_env){
jni_throw(env, "java/lang/IllegalStateException", "Server java env variable already set");
goto end;
}
server_env = env;
JniCallback = (*env)->NewGlobalRef(env, callback);
ret = server_bind();
if (ret==-1){
jni_throw(env, "java/lang/IllegalStateException", "Failed to bind sockets");
goto end;
}
{
jstring str = (jstring)(*env)->NewStringUTF(env, instance_path());
(*env)->CallVoidMethod(env, callback, started, str, getpid(), mdp_loopback_port, httpd_server_port);
(*env)->DeleteLocalRef(env, str);
}
server_loop(waiting, wokeup);
end:
server_env=NULL;
if (JniCallback){
(*env)->DeleteGlobalRef(env, JniCallback);
JniCallback = NULL;
}
if (keyring)
keyring_free(keyring);
keyring = NULL;
return ret;
}

View File

@ -18,6 +18,7 @@
*/
#include <stdio.h>
#include "features.h"
#include "cli.h"
#include "serval_types.h"
#include "str.h"
@ -128,7 +129,7 @@ static int app_keyring_list(const struct cli_parsed *parsed, struct cli_context
"did",
"name"
};
cli_columns(context, 3, names);
cli_start_table(context, NELS(names), names);
size_t rowcount = 0;
keyring_iterator it;
@ -145,7 +146,7 @@ static int app_keyring_list(const struct cli_parsed *parsed, struct cli_context
rowcount++;
}
keyring_free(k);
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
return 0;
}
@ -454,7 +455,7 @@ static int app_id_list(const struct cli_parsed *parsed, struct cli_context *cont
const char *names[]={
"sid"
};
cli_columns(context, 1, names);
cli_start_table(context, NELS(names), names);
size_t rowcount=0;
time_ms_t timeout=gettime_ms()+5000;
@ -481,7 +482,7 @@ static int app_id_list(const struct cli_parsed *parsed, struct cli_context *cont
break;
}
}
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
end:
mdp_close(mdp_sock);
return ret;

7
log.c
View File

@ -1,6 +1,7 @@
/*
Serval DNA logging
Copyright 2013 Serval Project Inc.
Copyright (C) 2013-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -582,8 +583,7 @@ static void _open_log_file(_log_iterator *it)
static void _rotate_log_file(_log_iterator *it)
{
if (_log_file != NO_FILE && _log_file_path == _log_file_path_buf) {
assert(!cf_limbo);
if (!cf_limbo && _log_file != NO_FILE && _log_file_path == _log_file_path_buf) {
if (!config.log.file.path[0] && config.log.file.duration) {
_compute_file_start_time(it);
if (it->file_start_time != _log_file_start_time) {
@ -677,4 +677,3 @@ void logConfigChanged()
it.state->config_logged = 0;
logFlush();
}

53
main.c
View File

@ -1,6 +1,6 @@
/*
Serval DNA daemon
Copyright (C) 2012 Serval Project Inc.
Serval DNA main command-line entry point
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -17,54 +17,9 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <signal.h>
#include "serval.h"
#include "conf.h"
static void crash_handler(int signal);
extern int servald_main(int, char**);
int main(int argc, char **argv)
{
#if defined WIN32
WSADATA wsa_data;
WSAStartup(MAKEWORD(1,1), &wsa_data);
#endif
/* Catch crash signals so that we can log a backtrace before expiring. */
struct sigaction sig;
sig.sa_handler = crash_handler;
sigemptyset(&sig.sa_mask); // Don't block any signals during handler
sig.sa_flags = SA_NODEFER | SA_RESETHAND; // So the signal handler can kill the process by re-sending the same signal to itself
sigaction(SIGSEGV, &sig, NULL);
sigaction(SIGFPE, &sig, NULL);
sigaction(SIGILL, &sig, NULL);
sigaction(SIGBUS, &sig, NULL);
sigaction(SIGABRT, &sig, NULL);
/* Setup i/o signal handlers */
signal(SIGPIPE,sigPipeHandler);
signal(SIGIO,sigIoHandler);
cf_init();
int status = parseCommandLine(NULL, argv[0], argc - 1, (const char*const*)&argv[1]);
#if defined WIN32
WSACleanup();
#endif
return status;
return servald_main(argc, argv);
}
char crash_handler_clue[1024] = "no clue";
static void crash_handler(int signal)
{
LOGF(LOG_LEVEL_FATAL, "Caught signal %s", alloca_signal_name(signal));
LOGF(LOG_LEVEL_FATAL, "The following clue may help: %s", crash_handler_clue);
dump_stack(LOG_LEVEL_FATAL);
BACKTRACE;
// Now die of the same signal, so that our exit status reflects the cause.
INFOF("Re-sending signal %d to self", signal);
kill(getpid(), signal);
// If that didn't work, then die normally.
INFOF("exit(%d)", -signal);
exit(-signal);
}

View File

@ -99,11 +99,11 @@ static int app_meshmb_read(const struct cli_parsed *parsed, struct cli_context *
return -1;
int ret=0;
int row_id=0;
size_t row_id = 0;
const char *names[]={
"_id","offset","age","message"
};
cli_columns(context, 4, names);
cli_start_table(context, NELS(names), names);
time_s_t timestamp = 0;
time_s_t now = gettime();
@ -134,6 +134,7 @@ static int app_meshmb_read(const struct cli_parsed *parsed, struct cli_context *
break;
}
}
cli_end_table(context, row_id);
message_ply_read_close(&read);
return ret;
@ -169,7 +170,7 @@ static int app_meshmb_find(const struct cli_parsed *parsed, struct cli_context *
"date",
"name"
};
cli_columns(context, 5, names);
cli_start_table(context, NELS(names), names);
unsigned rowcount=0;
int n;
@ -184,7 +185,7 @@ static int app_meshmb_find(const struct cli_parsed *parsed, struct cli_context *
cli_put_string(context, m->name, "\n");
}
rhizome_list_release(&cursor);
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
return 0;
}
@ -212,4 +213,4 @@ static int app_meshmb_news(const struct cli_parsed *parsed, struct cli_context *
{
return 0;
}
*/
*/

View File

@ -47,7 +47,7 @@ static int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_
"_id","recipient","read", "last_message", "read_offset", "message"
};
cli_columns(context, include_message? 6: 5, names);
cli_start_table(context, include_message? NELS(names) : NELS(names) - 1, names);
int rows = 0;
if (conv) {
struct meshms_conversation_iterator it;
@ -81,7 +81,7 @@ static int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_
}
}
}
cli_row_count(context, rows);
cli_end_table(context, rows);
status=MESHMS_STATUS_OK;
end:
@ -166,7 +166,7 @@ static int app_meshms_list_messages(const struct cli_parsed *parsed, struct cli_
const char *names[]={
"_id","offset","age","type","message"
};
cli_columns(context, 5, names);
cli_start_table(context, NELS(names), names);
bool_t marked_delivered = 0;
bool_t marked_read = 0;
time_s_t now = gettime();
@ -210,7 +210,7 @@ static int app_meshms_list_messages(const struct cli_parsed *parsed, struct cli_
}
}
if (!meshms_failed(status))
cli_row_count(context, id);
cli_end_table(context, id);
meshms_message_iterator_close(&iter);
keyring_free(keyring);
keyring = NULL;

View File

@ -1,21 +1,22 @@
/*
Serval network command line functions
Copyright (C) 2014 Serval Project Inc.
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.
*/
Serval network command line
Copyright (C) 2014 Serval Project Inc.
Copyright (C) 2016 Flinders University
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.
*/
#include <unistd.h>
#include <stdint.h>
@ -336,7 +337,7 @@ static int app_trace(const struct cli_parsed *parsed, struct cli_context *contex
len = ob_get(b);
cli_put_long(context, i, ":");
uint8_t *sid = ob_get_bytes_ptr(b, len);
cli_put_string(context, alloca_tohex(sid, len), "\n");
cli_put_hexvalue(context, sid, len, "\n");
i++;
}
ret = 0;
@ -392,7 +393,7 @@ static int app_id_self(const struct cli_parsed *parsed, struct cli_context *cont
const char *names[]={
"sid"
};
cli_columns(context, 1, names);
cli_start_table(context, NELS(names), names);
size_t rowcount=0;
do{
@ -419,13 +420,13 @@ static int app_id_self(const struct cli_parsed *parsed, struct cli_context *cont
unsigned i;
for(i=0;i<a.addrlist.frame_sid_count;i++) {
rowcount++;
cli_put_string(context, alloca_tohex_sid_t(a.addrlist.sids[i]), "\n");
cli_put_hexvalue(context, a.addrlist.sids[i].binary, sizeof(a.addrlist.sids[i].binary), "\n");
}
/* get ready to ask for next block of SIDs */
a.packetTypeAndFlags=MDP_GETADDRS;
a.addrlist.first_sid=a.addrlist.last_sid+1;
}while(a.addrlist.frame_sid_count==MDP_MAX_SID_REQUEST);
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
overlay_mdp_client_close(mdp_sockfd);
return 0;
}
@ -496,7 +497,7 @@ static int app_route_print(const struct cli_parsed *parsed, struct cli_context *
"Next hop",
"Prior hop"
};
cli_columns(context, 5, names);
cli_start_table(context, NELS(names), names);
size_t rowcount=0;
sigIntFlag = 0;
@ -554,7 +555,7 @@ static int app_route_print(const struct cli_parsed *parsed, struct cli_context *
}
}
cli_put_string(context, alloca_tohex_sid_t(*sid), ":");
cli_put_hexvalue(context, sid->binary, sizeof(sid->binary), ":");
char flags[32];
strbuf b = strbuf_local_buf(flags);
@ -579,8 +580,8 @@ static int app_route_print(const struct cli_parsed *parsed, struct cli_context *
}
cli_put_string(context, strbuf_str(b), ":");
cli_put_string(context, interface_name, ":");
cli_put_string(context, next_hop?alloca_tohex_sid_t(*next_hop):"", ":");
cli_put_string(context, prior_hop?alloca_tohex_sid_t(*prior_hop):"", "\n");
cli_put_hexvalue(context, next_hop ? next_hop->binary : NULL, next_hop ? sizeof(next_hop->binary) : 0, ":");
cli_put_hexvalue(context, prior_hop ? prior_hop->binary : NULL, prior_hop ? sizeof(prior_hop->binary) : 0, "\n");
rowcount++;
}
}
@ -592,7 +593,7 @@ static int app_route_print(const struct cli_parsed *parsed, struct cli_context *
signal(SIGINT, SIG_DFL);
sigIntFlag = 0;
ret = 0;
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
end:
mdp_close(mdp_sockfd);
@ -750,7 +751,7 @@ static int app_dna_lookup(const struct cli_parsed *parsed, struct cli_context *c
"did",
"name"
};
cli_columns(context, 3, names);
cli_start_table(context, NELS(names), names);
size_t rowcount = 0;
while (timeout > (now = gettime_ms())){
@ -813,7 +814,7 @@ static int app_dna_lookup(const struct cli_parsed *parsed, struct cli_context *c
}
overlay_mdp_client_close(mdp_sockfd);
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
return 0;
}
@ -904,8 +905,9 @@ static int app_reverse_lookup(const struct cli_parsed *parsed, struct cli_contex
char did[DID_MAXSIZE + 1];
char name[64];
char uri[512];
sid_t sid;
if ( !parseDnaReply((char *)mdp_reply.out.payload, mdp_reply.out.payload_length, sidhex, did, name, uri, NULL)
|| !str_is_subscriber_id(sidhex)
|| str_to_sid_t(&sid, sidhex) == -1
|| !str_is_did(did)
|| !str_is_uri(uri)
) {
@ -915,8 +917,9 @@ static int app_reverse_lookup(const struct cli_parsed *parsed, struct cli_contex
}
/* Got a good DNA reply, copy it into place and stop polling */
cli_field_name(context, "sid", ":");
cli_put_string(context, sidhex, "\n");
cli_put_hexvalue(context, sid.binary, sizeof(sid.binary), "\n");
cli_field_name(context, "did", ":");
cli_put_string(context, did, "\n");
cli_field_name(context, "name", ":");

View File

@ -610,8 +610,7 @@ static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_conte
if (strcmp(manifestpath, "-") == 0) {
// always extract a manifest to stdout, even if writing the file itself failed.
cli_field_name(context, "manifest", ":");
cli_write(context, m->manifestdata, m->manifest_all_bytes);
cli_delim(context, "\n");
cli_put_blob(context, m->manifestdata, m->manifest_all_bytes, "\n");
} else if (!zip_comment) {
int append = (strcmp(manifestpath, filepath)==0)?1:0;
// don't write out the manifest if we were asked to append it and writing the file failed.
@ -746,7 +745,7 @@ static int app_rhizome_list(const struct cli_parsed *parsed, struct cli_context
"recipient",
"name"
};
cli_columns(context, NELS(headers), headers);
cli_start_table(context, NELS(headers), headers);
size_t rowcount = 0;
int n;
while ((n = rhizome_list_next(&cursor)) == 1) {
@ -796,7 +795,7 @@ static int app_rhizome_list(const struct cli_parsed *parsed, struct cli_context
keyring = NULL;
if (n == -1)
return -1;
cli_row_count(context, rowcount);
cli_end_table(context, rowcount);
return 0;
}

View File

@ -19,6 +19,8 @@
#ifndef __SERVAL_DNA__SECTION_H
#define __SERVAL_DNA__SECTION_H
#include "features.h"
/* Macros for creating named linkage sections.
*/

View File

@ -152,9 +152,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
extern const char version_servald[];
extern const char copyright_servald[];
struct cli_parsed;
int rhizome_enabled();
int rhizome_http_server_running();
@ -216,8 +213,6 @@ void logServalPacket(int level, struct __sourceloc __whence, const char *message
int rhizome_opendb();
int parseCommandLine(struct cli_context *context, const char *argv0, int argc, const char *const *argv);
int allow_inbound_packet(const struct internal_mdp_header *header);
int allow_outbound_packet(const struct internal_mdp_header *header);
void load_mdp_packet_rules(const char *filename);

73
servald_main.c Normal file
View File

@ -0,0 +1,73 @@
/*
Serval DNA main command-line entry point
Copyright (C) 2012 Serval Project Inc.
Copyright (C) 2016 Flinders University
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.
*/
#include <signal.h>
#include "commandline.h"
#include "sighandlers.h"
#include "conf.h"
static void crash_handler(int signal);
int servald_main(int argc, char **argv)
{
#if defined WIN32
WSADATA wsa_data;
WSAStartup(MAKEWORD(1,1), &wsa_data);
#endif
/* Catch crash signals so that we can log a backtrace before expiring. */
struct sigaction sig;
sig.sa_handler = crash_handler;
sigemptyset(&sig.sa_mask); // Don't block any signals during handler
sig.sa_flags = SA_NODEFER | SA_RESETHAND; // So the signal handler can kill the process by re-sending the same signal to itself
sigaction(SIGSEGV, &sig, NULL);
sigaction(SIGFPE, &sig, NULL);
sigaction(SIGILL, &sig, NULL);
sigaction(SIGBUS, &sig, NULL);
sigaction(SIGABRT, &sig, NULL);
/* Setup i/o signal handlers */
signal(SIGPIPE,sigPipeHandler);
signal(SIGIO,sigIoHandler);
cf_init();
int status = commandline_main_stdio(stdout, argv[0], argc - 1, (const char*const*)&argv[1]);
#if defined WIN32
WSACleanup();
#endif
return status;
}
char crash_handler_clue[1024] = "no clue";
static void crash_handler(int signal)
{
LOGF(LOG_LEVEL_FATAL, "Caught signal %s", alloca_signal_name(signal));
LOGF(LOG_LEVEL_FATAL, "The following clue may help: %s", crash_handler_clue);
dump_stack(LOG_LEVEL_FATAL);
BACKTRACE;
// Now die of the same signal, so that our exit status reflects the cause.
INFOF("Re-sending signal %d to self", signal);
kill(getpid(), signal);
// If that didn't work, then die normally.
INFOF("exit(%d)", -signal);
exit(-signal);
}

503
server.c
View File

@ -1,6 +1,8 @@
/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2010 Paul Gardner-Stephen
/*
Serval DNA server main loop
Copyright (C) 2010 Paul Gardner-Stephen
Copyright (C) 2011-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -17,7 +19,6 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <assert.h>
#include <dirent.h>
#include <signal.h>
@ -27,15 +28,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#ifdef HAVE_LINUX_THREADS
#include <sys/syscall.h>
#endif
#include "server.h"
#include "serval.h"
#include "conf.h"
#include "str.h"
#include "numeric_str.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
#include "overlay_interface.h"
#include "overlay_packet.h"
#include "server.h"
#include "keyring.h"
#include "commandline.h"
#include "mdp_client.h"
@ -45,14 +51,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#define PIDFILE_NAME "servald.pid"
#define STOPFILE_NAME "servald.stop"
__thread int serverMode = 0;
__thread enum server_mode serverMode = SERVER_NOT_RUNNING;
__thread keyring_file *keyring=NULL;
struct pid_tid {
pid_t pid;
pid_t tid;
};
static char pidfile_path[256];
static int server_getpid = 0;
static struct pid_tid server_pid_tid = { .pid = 0, .tid = 0 };
static int server_pidfd = -1;
static int server_bind();
static void server_loop();
static int server();
static int server_write_pid();
static void signal_handler(int signal);
@ -60,44 +69,68 @@ static void serverCleanUp();
static const char *_server_pidfile_path(struct __sourceloc __whence);
#define server_pidfile_path() (_server_pidfile_path(__WHENCE__))
void cli_cleanup(){
static int server_write_proc_state(const char *path, const char *fmt, ...);
static int server_get_proc_state(const char *path, char *buff, size_t buff_len);
void cli_cleanup()
{
/* clean up after ourselves */
rhizome_close_db();
free_subscribers();
assert(keyring==NULL);
}
/** Return the PID of the currently running server process, return 0 if there is none.
*/
int server_pid()
static pid_t gettid()
{
// Note that if we close another handle on the same file, our lock will disappear.
if (server_getpid == getpid())
return server_getpid;
#ifdef HAVE_LINUX_THREADS
return syscall(SYS_gettid);
#else
return getpid();
#endif
}
static struct pid_tid get_server_pid_tid()
{
// If the server process closes another handle on the same file, its lock will disappear, so this
// guards against that happening.
if (server_pid_tid.pid == getpid())
return server_pid_tid;
// Attempt to read the pid, and optionally the tid (Linux thread ID) from the pid file.
char dirname[1024];
if (!FORMF_SERVAL_RUN_PATH(dirname, NULL))
return -1;
goto error;
struct stat st;
if (stat(dirname, &st) == -1)
return WHYF_perror("stat(%s)", alloca_str_toprint(dirname));
if ((st.st_mode & S_IFMT) != S_IFDIR)
return WHYF("Not a directory: %s", dirname);
if (stat(dirname, &st) == -1) {
WHYF_perror("stat(%s)", alloca_str_toprint(dirname));
goto error;
}
if ((st.st_mode & S_IFMT) != S_IFDIR) {
WHYF("Not a directory: %s", dirname);
goto error;
}
const char *ppath = server_pidfile_path();
if (ppath == NULL)
return -1;
goto error;
const char *p = strrchr(ppath, '/');
assert(p != NULL);
int pid = -1;
int32_t pid = -1;
int32_t tid = -1;
int fd = open(ppath, O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
return WHYF_perror("open(%s, O_RDONLY)", alloca_str_toprint(ppath));
if (errno != ENOENT) {
WHYF_perror("open(%s, O_RDONLY)", alloca_str_toprint(ppath));
goto error;
}
} else {
char buf[20];
char buf[30];
ssize_t len = read(fd, buf, sizeof buf);
if (len>0){
buf[len]=0;
pid = atoi(buf);
if (len > 0 && (size_t)len < sizeof buf) {
buf[len] = '\0';
const char *e = NULL;
if (str_to_int32(buf, 10, &pid, &e) && *e == ' ')
str_to_int32(e + 1, 10, &tid, NULL);
else
tid = pid;
}
if (pid > 0){
// check that the server process still holds a lock
@ -109,18 +142,43 @@ int server_pid()
lock.l_len = len;
fcntl(fd, F_GETLK, &lock);
if (lock.l_type == F_UNLCK || lock.l_pid != pid)
pid = -1;
pid = tid = -1;
}
close(fd);
if (pid > 0)
return pid;
return (struct pid_tid){ .pid = pid, .tid = tid };
INFOF("Unlinking stale pidfile %s", ppath);
unlink(ppath);
}
return (struct pid_tid){ .pid = 0, .tid = 0 };
error:
return (struct pid_tid){ .pid = -1, .tid = -1 };
}
// Send a signal to a given process. Returns 0 if sent, 1 if not sent because the process is non
// existent (ESRCH), or -1 if not sent due to another error (eg, EPERM).
static int send_signal(const struct pid_tid *id, int signum)
{
#ifdef HAVE_LINUX_THREADS
if (id->tid > 0) {
if (syscall(SYS_tgkill, id->pid, id->tid, signum) == -1) {
if (errno == ESRCH)
return 1;
WHYF_perror("Cannot send %s to Servald pid=%d tid=%d (pidfile %s)", alloca_signal_name(signum), id->pid, id->tid, server_pidfile_path());
return -1;
}
return 0;
}
#endif // !HAVE_LINUX_THREADS
if (kill(id->pid, signum) == -1) {
if (errno == ESRCH)
return 1;
WHYF_perror("Cannot send %s to Servald pid=%d (pidfile %s)", alloca_signal_name(signum), id->pid, server_pidfile_path());
return -1;
}
return 0;
}
#define server_pidfile_path() (_server_pidfile_path(__WHENCE__))
static const char *_server_pidfile_path(struct __sourceloc __whence)
{
if (!pidfile_path[0]) {
@ -130,145 +188,12 @@ static const char *_server_pidfile_path(struct __sourceloc __whence)
return pidfile_path;
}
#ifdef HAVE_JNI_H
JNIEnv *server_env=NULL;
jclass IJniServer= NULL;
jmethodID aboutToWait, wokeUp, started;
jobject JniCallback;
JNIEXPORT jint JNICALL Java_org_servalproject_servaldna_ServalDCommand_server(
JNIEnv *env, jobject UNUSED(this), jobject callback, jobject keyring_pin, jobjectArray entry_pins)
int server_pid()
{
if (!IJniServer){
IJniServer = (*env)->FindClass(env, "org/servalproject/servaldna/IJniServer");
if (IJniServer==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate class org.servalproject.servaldna.IJniServer");
// make sure the interface class cannot be garbage collected between invocations
IJniServer = (jclass)(*env)->NewGlobalRef(env, IJniServer);
if (IJniServer==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to create global ref to class org.servalproject.servaldna.IJniServer");
aboutToWait = (*env)->GetMethodID(env, IJniServer, "aboutToWait", "(JJJ)J");
if (aboutToWait==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method aboutToWait");
wokeUp = (*env)->GetMethodID(env, IJniServer, "wokeUp", "()V");
if (wokeUp==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method wokeUp");
started = (*env)->GetMethodID(env, IJniServer, "started", "(Ljava/lang/String;III)V");
if (started==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method started");
}
int pid = server_pid();
if (pid < 0)
return Throw(env, "java/lang/IllegalStateException", "Failed to read server pid ");
if (pid>0)
return Throw(env, "java/lang/IllegalStateException", "Server already running on pid ");
cf_reload_strict();
int ret = -1;
{
const char *cpin = keyring_pin?(*env)->GetStringUTFChars(env, keyring_pin, NULL):NULL;
if (cpin != NULL){
keyring = keyring_open_instance(cpin);
(*env)->ReleaseStringUTFChars(env, keyring_pin, cpin);
}else{
keyring = keyring_open_instance("");
}
}
// Always open all PIN-less entries.
keyring_enter_pin(keyring, "");
if (entry_pins){
jsize len = (*env)->GetArrayLength(env, entry_pins);
jsize i;
for (i = 0; i < len; ++i) {
const jstring pin = (jstring)(*env)->GetObjectArrayElement(env, entry_pins, i);
if ((*env)->ExceptionCheck(env))
goto end;
const char *cpin = (*env)->GetStringUTFChars(env, pin, NULL);
if (cpin != NULL){
keyring_enter_pin(keyring, cpin);
(*env)->ReleaseStringUTFChars(env, pin, cpin);
}
}
}
if (server_env){
Throw(env, "java/lang/IllegalStateException", "Server java env variable already set");
goto end;
}
server_env = env;
JniCallback = (*env)->NewGlobalRef(env, callback);
ret = server_bind();
if (ret==-1){
Throw(env, "java/lang/IllegalStateException", "Failed to bind sockets");
goto end;
}
{
jstring str = (jstring)(*env)->NewStringUTF(env, instance_path());
(*env)->CallVoidMethod(env, callback, started, str, getpid(), mdp_loopback_port, httpd_server_port);
(*env)->DeleteLocalRef(env, str);
}
server_loop();
end:
server_env=NULL;
if (JniCallback){
(*env)->DeleteGlobalRef(env, JniCallback);
JniCallback = NULL;
}
if (keyring)
keyring_free(keyring);
keyring = NULL;
return ret;
return get_server_pid_tid().pid;
}
static time_ms_t waiting(time_ms_t now, time_ms_t next_run, time_ms_t next_wakeup)
{
if (server_env && JniCallback){
jlong r = (*server_env)->CallLongMethod(server_env, JniCallback, aboutToWait, (jlong)now, (jlong)next_run, (jlong)next_wakeup);
// stop the server if there are any issues
if ((*server_env)->ExceptionCheck(server_env)){
serverMode=SERVER_CLOSING;
INFO("Stopping server due to exception");
return now;
}
return r;
}
return next_wakeup;
}
static void wokeup()
{
if (server_env && JniCallback){
(*server_env)->CallVoidMethod(server_env, JniCallback, wokeUp);
// stop the server if there are any issues
if ((*server_env)->ExceptionCheck(server_env)){
INFO("Stopping server due to exception");
serverMode=SERVER_CLOSING;
}
}
}
#else
#define waiting NULL
#define wokeup NULL
#endif
static int server_bind()
int server_bind()
{
serverMode = SERVER_RUNNING;
@ -346,16 +271,18 @@ static int server_bind()
return 0;
}
static void server_loop()
void server_loop(time_ms_t (*waiting)(time_ms_t, time_ms_t, time_ms_t), void (*wokeup)())
{
cf_on_config_change();
// log message used by tests to wait for the server to start
// This log message is used by tests to wait for the server to start.
INFOF("Server initialised, entering main loop");
/* Check for activitiy and respond to it */
while((serverMode==SERVER_RUNNING) && fd_poll2(waiting, wokeup))
while ((serverMode == SERVER_RUNNING) && fd_poll2(waiting, wokeup))
;
INFOF("Server finished, exiting main loop");
serverCleanUp();
if (server_pidfd!=-1){
@ -371,13 +298,10 @@ static int server()
IN();
if (server_bind()==-1)
RETURN(-1);
server_loop();
// note that we haven't tried to free all types of allocated memory used by the server.
// so it's safer to force this process to close, instead of trying to release everything.
exit(0);
OUT();
server_loop(NULL, NULL);
RETURN(0);
}
static int server_write_pid()
@ -394,16 +318,21 @@ static int server_write_pid()
if (fd==-1)
return WHYF_perror("open(%s, O_RDWR | O_CREAT)", alloca_str_toprint(ppath));
int pid = server_getpid = getpid();
char buf[20];
int len = snprintf(buf, sizeof buf, "%d", pid);
int32_t pid = server_pid_tid.pid = getpid();
int32_t tid = server_pid_tid.tid = gettid();
strbuf sb = strbuf_alloca(30);
strbuf_sprintf(sb, "%" PRId32, pid);
if (tid != pid)
strbuf_sprintf(sb, " %" PRId32, tid);
assert(!strbuf_overrun(sb));
struct flock lock;
bzero(&lock, sizeof lock);
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = len;
lock.l_len = strbuf_len(sb);
if (fcntl(fd, F_SETLK, &lock)==-1){
close(fd);
return WHYF_perror("fcntl(%d, F_SETLK, &lock)", fd);
@ -414,9 +343,9 @@ static int server_write_pid()
return WHYF_perror("ftruncate(%d, 0)", fd);
}
if (write(fd, buf, len)!=len){
if (write(fd, strbuf_str(sb), strbuf_len(sb)) != (ssize_t)strbuf_len(sb)){
close(fd);
return WHYF_perror("write(%d, %p, %d)", fd, buf, len);
return WHYF_perror("write(%d, %s, %d)", fd, alloca_str_toprint(strbuf_str(sb)), strbuf_len(sb));
}
// leave the pid file open!
@ -431,7 +360,7 @@ static int get_proc_path(const char *path, char *buf, size_t bufsiz)
return 0;
}
int server_write_proc_state(const char *path, const char *fmt, ...)
static int server_write_proc_state(const char *path, const char *fmt, ...)
{
char path_buf[400];
if (get_proc_path(path, path_buf, sizeof path_buf)==-1)
@ -457,7 +386,7 @@ int server_write_proc_state(const char *path, const char *fmt, ...)
return 0;
}
int server_get_proc_state(const char *path, char *buff, size_t buff_len)
static int server_get_proc_state(const char *path, char *buff, size_t buff_len)
{
char path_buf[400];
if (get_proc_path(path, path_buf, sizeof path_buf)==-1)
@ -649,40 +578,96 @@ static void clean_proc()
static void serverCleanUp()
{
assert(serverMode);
assert(serverMode != SERVER_NOT_RUNNING);
INFOF("Server cleaning up");
rhizome_close_db();
dna_helper_shutdown();
overlay_interface_close_all();
overlay_mdp_clean_socket_files();
release_my_subscriber();
serverMode = 0;
serverMode = SERVER_NOT_RUNNING;
clean_proc();
}
static void signal_handler(int signal)
static void signal_handler(int signum)
{
switch (signal) {
switch (signum) {
case SIGIO:
// noop to break out of poll
return;
case SIGHUP:
case SIGINT:
/* Trigger the server to close gracefully after any current alarm has completed.
If we get a second signal, exit now.
*/
if (serverMode==SERVER_RUNNING){
INFO("Attempting clean shutdown");
serverMode=SERVER_CLOSING;
return;
switch (serverMode) {
case SERVER_RUNNING:
// Trigger the server to close gracefully after any current alarm has completed.
INFOF("Caught signal %s -- attempting clean shutdown", alloca_signal_name(signum));
serverMode = SERVER_CLOSING;
return;
case SERVER_CLOSING:
// If a second signal is received before the server has gracefully shut down, then forcibly
// terminate it immediately. If the server is running in a thread, then this will only call
// serverCleanUp() if the signal was received by the same thread that is running the server,
// because serverMode is thread-local. The zero exit status indicates a clean shutdown. So
// the "stop" command must send SIGHUP to the correct thread.
WARNF("Caught signal %s -- forced shutdown", alloca_signal_name(signum));
serverCleanUp();
exit(0);
case SERVER_NOT_RUNNING:
// If this thread is not running a server, then treat the signal as immediately fatal.
break;
}
// fall through...
default:
LOGF(LOG_LEVEL_FATAL, "Caught signal %s", alloca_signal_name(signal));
LOGF(LOG_LEVEL_FATAL, "Caught signal %s", alloca_signal_name(signum));
LOGF(LOG_LEVEL_FATAL, "The following clue may help: %s", crash_handler_clue);
dump_stack(LOG_LEVEL_FATAL);
break;
}
// Exit with a status code indicating the caught signal. This involves removing the signal
// handler for the caught signal then re-sending the same signal to ourself.
struct sigaction sig;
bzero(&sig, sizeof sig);
sig.sa_flags = 0;
sig.sa_handler = SIG_DFL;
sigemptyset(&sig.sa_mask);
sigaction(signum, &sig, NULL);
kill(getpid(), signum);
// Just in case...
FATALF("Sending %s to self (pid=%d) did not cause exit", alloca_signal_name(signum));
}
static void cli_server_details(struct cli_context *context, const struct pid_tid *id)
{
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
cli_field_name(context, "status", ":");
cli_put_string(context, id->pid > 0 ? "running" : "stopped", "\n");
if (id->pid > 0) {
cli_field_name(context, "pid", ":");
cli_put_long(context, id->pid, "\n");
if (id->tid > 0 && id->tid != id->pid) {
cli_field_name(context, "tid", ":");
cli_put_long(context, id->tid, "\n");
}
char buff[256];
if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){
cli_field_name(context, "http_port", ":");
cli_put_string(context, buff, "\n");
}
if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){
cli_field_name(context, "mdp_inet_port", ":");
cli_put_string(context, buff, "\n");
}
}
serverCleanUp();
exit(0);
}
DEFINE_CMD(app_server_start, 0,
@ -706,14 +691,14 @@ static int app_server_start(const struct cli_parsed *parsed, struct cli_context
network interfaces that we will take interest in. */
if (config.interfaces.ac == 0)
NOWHENCE(WARN("No network interfaces configured (empty 'interfaces' config option)"));
int pid = server_pid();
if (pid < 0)
struct pid_tid id = get_server_pid_tid();
if (id.pid < 0)
RETURN(-1);
int ret = -1;
// If the pidfile identifies this process, it probably means we are re-spawning after a SEGV, so
// go ahead and do the fork/exec.
if (pid > 0 && pid != getpid()) {
WARNF("Server already running (pid=%d)", pid);
if (id.pid > 0 && id.pid != getpid()) {
WARNF("Server already running (pid=%d)", id.pid);
ret = 10;
} else {
if (foregroundP)
@ -735,6 +720,8 @@ static int app_server_start(const struct cli_parsed *parsed, struct cli_context
}
if (foregroundP) {
ret = server();
// Warning: The server is not rigorous about freeing all memory it allocates, so to avoid
// memory leaks, the caller should exit() immediately.
goto exit;
}
const char *dir = getenv("SERVALD_SERVER_CHDIR");
@ -812,33 +799,16 @@ static int app_server_start(const struct cli_parsed *parsed, struct cli_context
time_ms_t timeout = gettime_ms() + 5000;
do {
sleep_ms(200); // 5 Hz
} while ((pid = server_pid()) == 0 && gettime_ms() < timeout);
if (pid == -1)
} while ((id = get_server_pid_tid()).pid == 0 && gettime_ms() < timeout);
if (id.pid == -1)
goto exit;
if (pid == 0) {
if (id.pid == 0) {
WHY("Server process did not start");
goto exit;
}
ret = 0;
}
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
char buff[256];
if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){
cli_field_name(context, "http_port", ":");
cli_put_string(context, buff, "\n");
}
if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){
cli_field_name(context, "mdp_inet_port", ":");
cli_put_string(context, buff, "\n");
}
cli_server_details(context, &id);
cli_flush(context);
/* Sleep before returning if env var is set. This is used in testing, to simulate the situation
on Android phones where the "start" command is invoked via the JNI interface and the calling
@ -863,47 +833,39 @@ DEFINE_CMD(app_server_stop,CLIFLAG_PERMISSIVE_CONFIG,
static int app_server_stop(const struct cli_parsed *parsed, struct cli_context *context)
{
DEBUG_cli_parsed(verbose, parsed);
int pid, tries, running;
time_ms_t timeout;
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
pid = server_pid();
const struct pid_tid id = get_server_pid_tid();
cli_server_details(context, &id);
/* Not running, nothing to stop */
if (pid <= 0)
if (id.pid <= 0)
return 1;
INFOF("Stopping server (pid=%d)", pid);
INFOF("Stopping server (pid=%d)", id.pid);
/* Set the stop file and signal the process */
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
tries = 0;
running = pid;
while (running == pid) {
unsigned tries = 0;
pid_t running = id.pid;
while (running == id.pid) {
if (tries >= 5) {
WHYF("Servald pid=%d (pidfile=%s) did not stop after %d SIGHUP signals",
pid, server_pidfile_path(), tries);
id.pid, server_pidfile_path(), tries);
return 253;
}
++tries;
if (kill(pid, SIGHUP) == -1) {
// ESRCH means process is gone, possibly we are racing with another stop, or servald just died
// voluntarily. We DO NOT call serverCleanUp() in this case (once used to!) because that
// would race with a starting server process.
if (errno == ESRCH)
break;
WHY_perror("kill");
WHYF("Error sending SIGHUP to Servald pid=%d (pidfile %s)", pid, server_pidfile_path());
switch (send_signal(&id, SIGHUP)) {
case -1:
return 252;
case 0: {
// Allow a few seconds for the process to die.
time_ms_t timeout = gettime_ms() + 2000;
do
sleep_ms(200); // 5 Hz
while ((running = get_server_pid_tid().pid) == id.pid && gettime_ms() < timeout);
}
break;
default:
// Process no longer exists. DO NOT call serverCleanUp() (once used to!) because that would
// race with a starting server process.
running = 0;
break;
}
/* Allow a few seconds for the process to die. */
timeout = gettime_ms() + 2000;
do
sleep_ms(200); // 5 Hz
while ((running = server_pid()) == pid && gettime_ms() < timeout);
}
cli_field_name(context, "tries", ":");
cli_put_long(context, tries, "\n");
@ -916,28 +878,7 @@ DEFINE_CMD(app_server_status,CLIFLAG_PERMISSIVE_CONFIG,
static int app_server_status(const struct cli_parsed *parsed, struct cli_context *context)
{
DEBUG_cli_parsed(verbose, parsed);
int pid = server_pid();
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
cli_field_name(context, "status", ":");
cli_put_string(context, pid > 0 ? "running" : "stopped", "\n");
if (pid > 0) {
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
char buff[256];
if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){
cli_field_name(context, "http_port", ":");
cli_put_string(context, buff, "\n");
}
if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){
cli_field_name(context, "mdp_inet_port", ":");
cli_put_string(context, buff, "\n");
}
}
return pid > 0 ? 0 : 1;
const struct pid_tid id = get_server_pid_tid();
cli_server_details(context, &id);
return id.pid > 0 ? 0 : 1;
}

View File

@ -1,5 +1,7 @@
/*
Copyright (C) 2012-2014 Serval Project Inc.
Serval DNA server main loop
Copyright (C) 2012-2015 Serval Project Inc.
Copyright (C) 2016 Flinders University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@ -19,8 +21,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef __SERVAL_DNA__SERVER_H
#define __SERVAL_DNA__SERVER_H
#define SERVER_RUNNING 1
#define SERVER_CLOSING 2
#include "fdqueue.h"
#include "os.h" // for time_ms_t
enum server_mode {
SERVER_NOT_RUNNING = 0,
SERVER_RUNNING = 1,
SERVER_CLOSING = 2
};
DECLARE_ALARM(server_shutdown_check);
DECLARE_ALARM(server_watchdog);
@ -28,10 +36,14 @@ DECLARE_ALARM(server_config_reload);
DECLARE_ALARM(rhizome_sync_announce);
DECLARE_ALARM(fd_periodicstats);
extern __thread int serverMode;
extern __thread enum server_mode serverMode;
/** Return the PID of the currently running server process, return 0 if there is none.
*/
int server_pid();
int server_write_proc_state(const char *path, const char *fmt, ...);
int server_get_proc_state(const char *path, char *buff, size_t buff_len);
int server_bind();
void server_loop(time_ms_t (*waiting)(time_ms_t, time_ms_t, time_ms_t), void (*wokeup)());
#endif // __SERVAL_DNA__SERVER_H

View File

@ -2,6 +2,8 @@
# they can be linked into executables other than servald.
SERVAL_CLIENT_SOURCES = \
cli.c \
cli_stdio.c \
commandline.c \
conf.c \
conf_om.c \
conf_parse.c \
@ -46,7 +48,8 @@ SQLITE3_SOURCES = $(SQLITE3_AMALGAMATION)/sqlite3.c
# The source files for building the Serval DNA daemon.
SERVAL_DAEMON_SOURCES = \
commandline.c \
main.c \
servald_main.c \
conf_cli.c \
rhizome_cli.c \
keyring_cli.c \
@ -60,7 +63,6 @@ SERVAL_DAEMON_SOURCES = \
keyring.c \
log.c \
lsif.c \
main.c \
radio_link.c \
meshms.c \
meshmb.c \
@ -112,9 +114,14 @@ SERVAL_DAEMON_SOURCES = \
fec-3.0.1/encode_rs_8.c \
fec-3.0.1/init_rs_char.c
SERVAL_DAEMON_JNI_SOURCES = \
jni_common.c \
jni_commandline.c \
jni_server.c
TEST_SOURCES = \
commandline.c \
main.c \
servald_main.c \
test_cli.c \
log_context.c \
log_stderr.c \

View File

@ -105,6 +105,7 @@ strbuf strbuf_append_shell_quotemeta(strbuf sb, const char *word);
* @author Andrew Bettison <andrew@servalproject.com>
*/
strbuf strbuf_append_argv(strbuf sb, int argc, const char *const *argv);
#define alloca_argv(argc, argv) strbuf_str(strbuf_append_argv(strbuf_alloca(strbuf_count(strbuf_append_argv(strbuf_alloca(0), (argc), (argv)))), (argc), (argv)))
/* Append a textual description of a process exit status as produced by wait(2)
* and waitpid(2).

View File

@ -25,8 +25,9 @@ source "${0%/*}/../testdefs_java.sh"
setup() {
setup_servald
executeOk_servald config \
set debug.verbose 1 \
set log.console.level debug
set debug.jni 1 \
set debug.verbose 1 \
set log.console.level debug
assert_echo_works
compile_java_classes
setup_servald_so
@ -40,8 +41,9 @@ assert_echo_works() {
doc_Echo="Serval JNI echo Hello world"
test_Echo() {
executeJavaOk org.servalproject.test.ServalDTests 'echo' '-e' 'Hello,\ttab' 'world\0!'
assertStdoutIs -e 'Hello,\ttab world\0! \n'
executeJavaOk org.servalproject.test.CommandLine 'echo' '-e' 'Hello,\ttab' 'world\0!'
tfw_cat --stdout --stderr
assertStdoutIs -e 'Hello,\ttab\nworld\0!\n'
}
doc_Delim="Serval non-JNI output delimiter environment variable"
@ -57,20 +59,21 @@ test_Delim() {
doc_Repeat="Serval JNI repeated calls in same process"
test_Repeat() {
executeJavaOk org.servalproject.test.ServalDTests repeat 50 'echo' 'Hello,' 'world!'
assertStdoutLineCount '==' 50
assertStdoutGrep --matches=50 '^Hello, world! $'
executeJavaOk org.servalproject.test.CommandLine --repeat 50 'echo' 'Hello,' 'world!'
assertStdoutLineCount '==' 100
assertStdoutGrep --matches=50 '^Hello,$'
assertStdoutGrep --matches=50 '^world!$'
}
doc_NullArg="Serval JNI null arguments dont throw exception"
test_NullArg() {
executeJavaOk org.servalproject.test.ServalDTests 'echo' '(null)'
executeJavaOk org.servalproject.test.CommandLine 'echo' '(null)'
tfw_cat --stdout --stderr
}
doc_help="Serval JNI returns help text"
test_help() {
executeJavaOk org.servalproject.test.ServalDTests 'help'
doc_version="Serval JNI returns version text"
test_version() {
executeJavaOk org.servalproject.test.CommandLine 'version'
assertStdoutGrep 'Serval DNA version '
}
@ -83,11 +86,13 @@ setup_PeerList() {
foreach_instance +A +B create_single_identity
start_servald_instances +A +B
set_instance +A
executeOk_servald 'id' 'peers'
tfw_cat --stdout --stderr
}
test_PeerList() {
executeJavaOk org.servalproject.test.CommandLine 'peers'
executeJavaOk org.servalproject.test.CommandLine 'id' 'peers'
tfw_cat --stdout --stderr
assertStdoutGrep "$SIDB"
tfw_cat --stdout
}
teardown_PeerList() {
stop_all_servald_servers
@ -108,10 +113,11 @@ setup_DnaLookup() {
foreach_instance +A +B create_single_identity
start_servald_instances +A +B
set_instance +A
executeOk_servald dna lookup "$DIDB" 10000
tfw_cat --stdout --stderr
}
test_DnaLookup() {
executeJavaOk --timeout=10 org.servalproject.test.CommandLine 'lookup'
tfw_cat --stdout --stderr
executeJavaOk --timeout=10 org.servalproject.test.ServalDTests 'lookup'
assertStdoutGrep "$SIDB"
}
teardown_DnaLookup() {
@ -145,7 +151,7 @@ setup_serviceDiscovery() {
set_instance +A
}
test_serviceDiscovery() {
executeJavaOk --timeout=10 org.servalproject.test.CommandLine 'service' 'test_name.*'
executeJavaOk --timeout=10 org.servalproject.test.ServalDTests 'service' 'test_name.*'
assertStdoutGrep "$SIDB"
assertStdoutGrep "\<test_name\.msp\.port=512\>"
tfw_cat --stdout --stderr
@ -166,10 +172,28 @@ setup_servaldThread() {
setup
set_instance +A
create_single_identity
executeOk_servald config \
set debug.jni 1 \
set debug.verbose 1 \
set log.console.level debug
}
test_servaldThread() {
executeJavaOk --timeout=10 org.servalproject.test.CommandLine 'server'
tfw_cat --stdout --stderr
fork %server executeJavaOk --stdout-file=server_stdout --stderr-file=server_stderr org.servalproject.test.ServalDTests 'server'
wait_until --timeout=10 grep '^Started server' server_stdout
PID=$($SED -n -e '/^Started server/s/.* pid=\([0-9]\+\).*/\1/p' server_stdout)
assert [ -n "$PID" ]
assert kill -0 $PID 2>/dev/null
executeOk_servald status
extract_stdout_keyvalue status 'status' '.*'
extract_stdout_keyvalue pid 'pid' '[0-9]\+'
extract_stdout_keyvalue tid 'tid' '[0-9]\+'
assert [ "$status" = running ]
assert [ "$pid" -eq "$PID" ]
assert [ "$tid" -ne "$PID" ]
sleep 1
executeOk_servald stop
fork_wait %server
tfw_cat server_stdout server_stderr
}
runTests "$@"