diff --git a/.gitignore b/.gitignore index 6e6eacd5..5ff5cdba 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ *.a *.suo .*.sw? +*.swiftdoc +*.swiftmodule /VERSION.txt /configure /autom4te.cache @@ -37,6 +39,7 @@ test.*.log /java-api/Makefile /java-api/classes/ /java-api/testclasses/ +/swift-daemon-api/build/ /swift-client-api/Makefile /swift-client-api/build/ /swift-client-api/swift-client-util diff --git a/INSTALL.md b/INSTALL.md index 899f94aa..db267d3f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -259,6 +259,11 @@ present][Swift development]: Swift language's run-time support and resultant library dependencies, so is not suitable for deployment. +* **ServalDNA.swiftmodule** + **ServalDNA.swiftdoc** + **libServalDNA.a** + are the [Swift Daemon API module][]. + * **swift-client-api/build/debug/ServalClient.swiftmodule** **swift-client-api/build/debug/ServalClient.swiftdoc** **swift-client-api/build/debug/libServalClient.a** @@ -330,6 +335,7 @@ This document is available under the [Creative Commons Attribution 4.0 Internati [gcc 6]: http://gcc.gnu.org/gcc-6/ [Notes for Developers]: ./doc/Development.md [Swift development]: ./doc/Development.md#swift +[Swift Daemon API module]: ./doc/Development.md#swift-daemon-api [Swift Client API module]: ./doc/Development.md#swift-client-api [OpenWRT]: ./doc/OpenWRT.md [Serval Infrastructure]: ./doc/Serval-Infrastructure.md diff --git a/Makefile.in b/Makefile.in index 37a0d88e..a254e970 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,5 @@ # Makefile.in for Serval DNA daemon and libraries +# nnnn # vim: noet ts=8 sts=0 sw=8 prefix=@prefix@ exec_prefix=@exec_prefix@ @@ -7,16 +8,24 @@ sbindir=@sbindir@ sysconfdir=@sysconfdir@ localstatedir=@localstatedir@ srcdir=@srcdir@ +abs_srcdir=@abs_srcdir@ +abs_builddir=@abs_builddir@ CC = @CC@ AR = @AR@ RANLIB = @RANLIB@ JAVAC= @JAVAC@ -SWIFTC= @SWIFTC@ -SWIFTCFLAGS= @SWIFTCFLAGS@ -I. -I $(srcdir) +SWIFTC= @SWIFTC@ +SWIFTCFLAGS= @SWIFTCFLAGS@ -I$(abs_builddir) -I$(abs_srcdir) SWIFTLIBS= @LIBS@ + +SWIFT_BUILD= @SWIFT_BUILD@ +SWIFT_BUILD_FLAGS= $(addprefix -Xswiftc , $(SWIFTCFLAGS)) + SWIFT_MODULE_NAME= ServalDNA +SWIFT_PACKAGE_DIR= $(srcdir)/swift-daemon-api +SWIFT_BUILD_DIR= $(abs_builddir)/swift-daemon-api/build INSTALL= install INSTALL_PROGRAM= $(INSTALL) @@ -166,13 +175,26 @@ endif # $(JAVAC) ifneq ($(SWIFTC),) # Only provide Swift targets if the Swift compiler is available. -all: swift-client-api +all: swift-daemon-api swift-client-api test: servaldswift -clean: swift-client-api-clean +clean: swift-daemon-api-clean swift-client-api-clean -.PHONY: swift-client-api swift-client-api-clean +.PHONY: swift-daemon-api swift-daemon-api-clean swift-client-api swift-client-api-clean + +libServalDNA.a $(SWIFT_MODULE_NAME).swiftmodule $(SWIFT_MODULE_NAME).swiftdoc: swift-daemon-api + cp $(SWIFT_BUILD_DIR)/debug/$(notdir $@) $@ + +swift-daemon-api: + mkdir -p $(SWIFT_BUILD_DIR) && \ + cd $(SWIFT_PACKAGE_DIR) && \ + $(SWIFT_BUILD) --build-path $(SWIFT_BUILD_DIR) $(SWIFT_BUILD_FLAGS) $(SWIFTDEFS) + +swift-daemon-api-clean: + $(RM) -r $(SWIFT_BUILD_DIR) \ + $(SWIFT_MODULE_NAME).swiftmodule \ + $(SWIFT_MODULE_NAME).swiftdoc swift-client-api: @mkdir -p swift-client-api @@ -369,12 +391,15 @@ ifneq ($(SWIFTC),) # Only provide Swift targets if the Swift compiler is availab # package. servaldswift: $(srcdir)/servaldswift.swift \ $(OBJSDIR_SERVALD)/servald_features.o \ - $(OBJSDIR_SERVALD)/log_output_console.o \ $(OBJSDIR_SERVALD)/log_output_file.o \ libservaldaemon.a \ - $(srcdir)/module.modulemap + $(SWIFT_MODULE_NAME).swiftmodule \ + libServalDNA.a @echo SWIFT $@ - @$(SWIFTC) -emit-executable $(SWIFTCFLAGS) $(SWIFTDEFS) -o $@ $(filter %.swift, $^) $(filter %.o, $^) $(filter %.a, $^) $(SWIFTLIBS) + @$(SWIFTC) -emit-executable -o $@ \ + $(SWIFTCFLAGS) $(SWIFTDEFS) \ + $(filter %.swift, $^) $(filter %.o, $^) $(filter %.a, $^) \ + $(SWIFTLIBS) endif # $(SWIFTC) @@ -413,7 +438,7 @@ libservalclient.a: _servalclient.a $(LIBSODIUM_A) libservalclient.so: $(LIB_SERVAL_OBJS) $(OBJSDIR_TOOLS)/version.o $(LIBSODIUM_A) @echo LINK $@ - @$(CC) -Wall -shared -o $@ $(LDFLAGS) $(filter %.o, $^) $(filter %.a, $^) + @$(CC) -Wall -shared -o $@ $(LDFLAGS) $(filter %.o %.a, $^) $(filter %.a, $^) .INTERMEDIATE: _monitorclient.a _monitorclient.a: $(MONITOR_CLIENT_OBJS) $(OBJSDIR_TOOLS)/version.o diff --git a/cli_stdio.c b/cli_stdio.c index 7a60f7db..96fba5a9 100644 --- a/cli_stdio.c +++ b/cli_stdio.c @@ -28,6 +28,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "log.h" #include "str.h" +/* An instance of struct cli_vtable that prints all fields to a stdio stream. This is used by the + * 'servald' daemon to print its output on stdout. + */ + static FILE *stdio_fp(struct cli_context *context) { return ((struct cli_context_stdio *)(context->context))->fp; diff --git a/doc/Development.md b/doc/Development.md index ce24c5dc..9cb39b01 100644 --- a/doc/Development.md +++ b/doc/Development.md @@ -241,10 +241,10 @@ Serval DNA supports [Swift][], the language that Apple recommend for developing iOS apps for their mobile devices such as phones and tablets. The `./configure` script [generated from configure.ac](#autotools) detects whether a [Swift 4][] compiler is present, or failing that, a [Swift 3][] compiler, and -if so, then produces a Makefile that will compile -[servaldswift.swift](../servaldswift.swift) into the *servaldswift* executable, -to prove that the Swift [module map](../module.modulemap) allows Swift source -code to invoke internal Serval DNA functions. +if so, then produces a Makefile that will compile [servaldswift.swift][] into +the *servaldswift* executable, to prove that the Swift [module +map](../module.modulemap) allows Swift source code to invoke internal Serval +DNA functions. The `./configure` script can be passed the following variables, either as environment variables or using the `VARNAME=value` syntax on its command line: @@ -255,12 +255,45 @@ environment variables or using the `VARNAME=value` syntax on its command line: * `SWIFTCFLAGS` extra command-line arguments to pass to the Swift compiler; analogous to `CFLAGS` for the C compiler +Swift Daemon API +---------------- + +Serval DNA provides a *Swift Daemon API* as a [Swift module][] called +**ServalDNA**, which provides access to some of the internal APIs of the Serval +DNA daemon by wrapping direct function invocations in Swift classes. + +The Swift daemon API is written as a set of [Swift][] "wrappers" around +internal functions and constants that are declared in C header files. Those +internals are exposed to Swift by the Swift [module map](../module.modulemap), +which defines a [Swift module][] called **servald** that has the following +submodules: + +* **servald.log** the Serval DNA logging API, including the log output API + +* **servald.cli** the Serval DNA [CLI API][] and the daemon's command-line + entry point + +The [CliContext][] Swift class provides an object-oriented interface to the +[CLI API][]. To capture the output from any CLI command, a Swift program can +simply subclass [CliContext][] and override its `write()` method and any other +methods as needed, then pass an instance of that subclass to the +[serval\_commandline\_main][] function. An example of how to do this is in +[servaldswift.swift][], which uses an instance of the [CliContextFile][] +subclass to print its output on standard output via a buffer. + +[servaldswift.swift][] shows how to capture Serval DNA log output in Swift +code, by providing an implementation of the delegate log output `print()` +function. This works because [Makefile.in][] includes `log_output_delegate.o` +in the link for the *servaldswift* executable, and omits the other log outputs. + Swift Client API ---------------- Serval DNA provides a *Swift Client API* as a [Swift module][] called **ServalClient**, which provides access to the services of the Serval DNA -daemon through its [REST API][]. +daemon through its [REST API][]. Once an iOS app has started a thread that is +running the daemon (invoked via the [Swift Daemon API](#swift-daemon-api)), the +client API can be used to communicate with the running daemon thread. The Swift client API is written entirely in [Swift][] using the [URLSession][] Foundation class and related classes as the HTTP client. The API is covered @@ -307,10 +340,16 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC [CC BY 4.0]: ../LICENSE-DOCUMENTATION.md [Serval DNA]: ../README.md [build]: ../INSTALL.md +[CLI API]: ./CLI-API.md [REST API]: ./REST-API.md [Keyring REST API]: ./REST-API-Keyring.md [test scripts]: ./Testing.md [configure.ac]: ../configure.ac +[Makefile.in]: ../Makefile.in +[servaldswift.swift]: ../servaldswift.swift +[CliContext]: ../swift-daemon-api/Sources/CliContext.swift +[CliContextFile]: ../swift-daemon-api/Sources/CliContextFile.swift +[serval\_commandline\_main]: ../swift-daemon-api/Sources/commandline.swift [autoconf]: http://www.gnu.org/software/autoconf/autoconf.html [autoconf macro archive]: http://www.gnu.org/software/autoconf-archive/ [GNU M4]: http://www.gnu.org/software/m4/m4.html diff --git a/jni_commandline.c b/jni_commandline.c index 952cb9bb..c6802a25 100644 --- a/jni_commandline.c +++ b/jni_commandline.c @@ -268,7 +268,8 @@ JNIEXPORT jint JNICALL Java_org_servalproject_servaldna_ServalDCommand_rawComman return (jint) status; } -/* Output primitives for JNI. +/* An instance of struct cli_vtable that passes all output fields to an IJniResults + * interface callback. */ static struct jni_context *jni_context(struct cli_context *cli_context) diff --git a/log_output_delegate.c b/log_output_delegate.c new file mode 100644 index 00000000..cf9ef6bf --- /dev/null +++ b/log_output_delegate.c @@ -0,0 +1,134 @@ +/* +Serval DNA logging output to a delegate +Copyright 2017 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 "log.h" +#include "log_output.h" +#include "log_output_delegate.h" +#include "strbuf.h" + +/* An implementation of the Serval logging API that constructs log messages in + * a buffer then passes the buffer to a delegate. + */ + +/* Private state for delegate log output. + */ + +struct log_output_delegate_state { + bool_t opened; + // Buffer to hold log messages before the log file is open and ready for writing: + struct strbuf strbuf; + char buf[8192]; +}; + +static struct log_output_delegate_state static_state = { + .opened = 0, + .strbuf = STRUCT_STRBUF_EMPTY, + .buf = "" +}; + +/* Functions for querying configuration. + */ + +static int log_delegate_minimum_level(const struct log_output *UNUSED(out)) +{ + return serval_log_delegate.minimum_level; +} + +static bool_t log_delegate_show_pid(const struct log_output *UNUSED(out)) +{ + return serval_log_delegate.show_pid; +} + +static bool_t log_delegate_show_time(const struct log_output *UNUSED(out)) +{ + return serval_log_delegate.show_time; +} + +/* Log output operations. + */ + +static inline struct log_output_delegate_state *_state(struct log_output *out) +{ + assert(out->state); + return (struct log_output_delegate_state *)(out->state); +} + +static bool_t is_log_delegate_available(const struct log_output_iterator *UNUSED(it)) +{ + return serval_log_delegate.print != NULL; +} + +static void log_delegate_open(struct log_output_iterator *it) +{ + struct log_output_delegate_state *state = _state(*it->output); + if (!state->opened) { + state->opened = 1; + if (serval_log_delegate.show_prolog) + serval_log_print_prolog(it); + } +} + +static void log_delegate_start_line(struct log_output_iterator *it, int UNUSED(level)) +{ + struct log_output_delegate_state *state = _state(*it->output); + strbuf sb = &state->strbuf; + assert(strbuf_len(sb) == 0); + strbuf_init(sb, state->buf, sizeof state->buf); + it->xpf = XPRINTF_STRBUF(sb); +} + +static void log_delegate_end_line(struct log_output_iterator *it, int level) +{ + struct log_output_delegate_state *state = _state(*it->output); + strbuf sb = &state->strbuf; + serval_log_delegate.print(level, strbuf_str(sb), strbuf_overrun(sb)); + strbuf_reset(sb); +} + +static void flush_log_delegate(struct log_output_iterator *UNUSED(it)) +{ + if (serval_log_delegate.flush) + serval_log_delegate.flush(); +} + +static struct log_output static_log_output = { + .minimum_level = log_delegate_minimum_level, + .show_pid = log_delegate_show_pid, + .show_time = log_delegate_show_time, + .state = &static_state, + .open = log_delegate_open, + .capture_fd = NULL, + .is_available = is_log_delegate_available, + .start_line = log_delegate_start_line, + .end_line = log_delegate_end_line, + .flush = flush_log_delegate, + .close = NULL +}; + +DEFINE_LOG_OUTPUT(&static_log_output); + +// These are defaults only; the delegate fills this in as it wishes. +struct log_delegate serval_log_delegate = { + .minimum_level = LOG_LEVEL_INFO, + .show_prolog = 1, + .show_pid = 0, + .show_time = 1, + .print = NULL, + .flush = NULL +}; diff --git a/log_output_delegate.h b/log_output_delegate.h new file mode 100644 index 00000000..1c2626a8 --- /dev/null +++ b/log_output_delegate.h @@ -0,0 +1,36 @@ +/* +Serval DNA logging output to a delegate +Copyright (C) 2017 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__LOG_OUTPUT_DELEGATE_H +#define __SERVAL_DNA__LOG_OUTPUT_DELEGATE_H + +#include "lang.h" // for bool_t + +struct log_delegate { + bool_t show_prolog; + int minimum_level; + bool_t show_pid; + bool_t show_time; + void (*print)(int level, const char *message, bool_t overrun); + void (*flush)(); +}; + +extern struct log_delegate serval_log_delegate; + +#endif // __SERVAL_DNA__LOG_OUTPUT_DELEGATE_H diff --git a/module.modulemap b/module.modulemap index b7268a11..6da13f4a 100644 --- a/module.modulemap +++ b/module.modulemap @@ -1,5 +1,5 @@ /* -Serval DNA module map +Serval DNA Swift module map Copyright (C) 2016-2017 Flinders University This program is free software; you can redistribute it and/or @@ -21,7 +21,14 @@ module servald { requires tls // thread-local storage - module main { - header "servald_main.h" + module cli { + header "commandline.h" + header "cli.h" + } + + module log { + header "log.h" + header "log_output.h" + header "log_output_delegate.h" } } diff --git a/servaldswift.swift b/servaldswift.swift index 539c481d..a8c51e5b 100644 --- a/servaldswift.swift +++ b/servaldswift.swift @@ -1,14 +1,54 @@ -import Foundation -import servald.main +/* +Serval DNA daemon in Swift +Copyright 2017 Flinders University -public func serval_daemon_main(args: [String]) -> CInt { - // print "args = \(args)" - var argv = args.map { strdup($0) } - argv.append(nil) - defer { - argv.forEach { free($0) } - } - return servald_main(CInt(argv.count - 1), &argv) +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. +*/ + +import Foundation +import ServalDNA +import servald.log + +// Logging +// +// A simplistic console log outputter that writes to standard error and is not +// configurable. Note that log_output_console.o is not linked into +// servaldswift, in order to avoid duplicate log outputs on standard error. + +private func logPrint(_ level: CInt, _ message: UnsafePointer?, _ overrun: Int8) { + let level_text = String(cString: serval_log_level_prefix_string(level)!) + let message_text = String(cString: message!) + FileHandle.standardError.write("\(level_text) \(message_text)\n".data(using:.utf8)!) } -exit(serval_daemon_main(args: CommandLine.arguments)) +serval_log_delegate.print = logPrint +serval_log_delegate.minimum_level = LOG_LEVEL_WARN +serval_log_delegate.show_prolog = 1 +serval_log_delegate.show_pid = 1 +serval_log_delegate.show_time = 1 + +// Output + +var contextFile = CliContextFile(FileHandle.standardOutput) + +// Invocation + +let status = serval_commandline_main(context: contextFile, args: CommandLine.arguments) + +// Cleanup + +contextFile.flush() + +exit(status) diff --git a/sourcefiles.mk b/sourcefiles.mk index 02370ec3..91c2b194 100644 --- a/sourcefiles.mk +++ b/sourcefiles.mk @@ -62,6 +62,7 @@ SERVAL_DAEMON_SOURCES = \ keyring_restful.c \ log_output_console.c \ log_output_file.c \ + log_output_delegate.c \ lsif.c \ radio_link.c \ meshms.c \ @@ -131,6 +132,12 @@ SERVAL_DAEMON_JNI_SOURCES = \ jni_instance.c \ jni_server.c +SERVAL_DAEMON_SWIFT_SOURCES = \ + swift_main.swift \ + swift_cli.swift \ + swift_cli_stdio.swift \ + swift_log.swift + MDP_CLIENT_SOURCES = \ mdp_client.c diff --git a/swift-client-api/Makefile.in b/swift-client-api/Makefile.in index b5d4de64..69d7a4eb 100644 --- a/swift-client-api/Makefile.in +++ b/swift-client-api/Makefile.in @@ -23,16 +23,18 @@ SWIFT_BUILD_FLAGS= $(addprefix -Xswiftc , $(SWIFTCFLAGS)) DEFS= @DEFS@ SWIFTDEFS= $(addprefix -Xcc , $(DEFS)) -.PHONY: all check check_swiftc check_swift_build swiftmodule clean +.PHONY: all +all: check package swift-client-util -all: check swiftmodule swift-client-util - -check: check_swiftc check_swift_build +.PHONY: check +check: check_swiftc check_swift_build +.PHONY: check_swiftc check_swiftc: @if [ -z "$(SWIFTC)" ]; then echo "No swift compiler configured" >&2; exit 1; fi @if ! $(SWIFTC) -version >/dev/null; then echo "Swift compiler not executable" >&2; exit 1; fi +.PHONY: check_swift_build check_swift_build: @if [ -z "$(SWIFT_BUILD)" ]; then echo "No swift package manager configured" >&2; exit 1; fi @if ! $(SWIFT_BUILD) -h >/dev/null; then echo "Swift package manager not executable" >&2; exit 1; fi @@ -40,17 +42,19 @@ check_swift_build: # The Swift 3 package manager --chdir option was replaced with --package-path # in Swift 4, so to support both, avoid the options altogether and use "cd # package-dir && swift build" instead. -swiftmodule: +.PHONY: package +package: mkdir -p $(SWIFT_BUILD_DIR) && \ cd $(SWIFT_PACKAGE_DIR) && \ $(SWIFT_BUILD) --build-path $(SWIFT_BUILD_DIR) $(SWIFT_BUILD_FLAGS) $(SWIFTDEFS) -$(SWIFT_BUILD_DIR)/debug/libServalClient.a: swiftmodule +$(SWIFT_BUILD_DIR)/debug/libServalClient.a: package swift-client-util: $(srcdir)/client_util.swift $(SWIFT_BUILD_DIR)/debug/libServalClient.a @echo SWIFT $@ @$(SWIFTC) -emit-executable $(SWIFTCFLAGS) $(SWIFTDEFS) -I $(SWIFT_BUILD_DIR)/debug -o $@ $^ +.PHONY: clean clean: $(RM) -r $(SWIFT_BUILD_DIR) $(RM) swift-client-util diff --git a/swift-daemon-api/Package.swift b/swift-daemon-api/Package.swift new file mode 100644 index 00000000..d18dde68 --- /dev/null +++ b/swift-daemon-api/Package.swift @@ -0,0 +1,9 @@ +import PackageDescription + +let package = Package( + name: "ServalDNA" + ) + +products.append( + Product(name: "ServalDNA", type: .Library(.Static), modules: "ServalDNA") + ) diff --git a/swift-daemon-api/Sources/CliContext.swift b/swift-daemon-api/Sources/CliContext.swift new file mode 100644 index 00000000..2e64e409 --- /dev/null +++ b/swift-daemon-api/Sources/CliContext.swift @@ -0,0 +1,158 @@ +/* +Serval DNA Swift CLI output +Copyright 2017 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. +*/ + +import Foundation +import servald.cli + +private extension Data { + var hexUpper: String { + return map { String(format: "%02hhX", $0) }.joined() + } +} + +open class CliContext { + public var cContext: cli_context = cli_context(context: nil, vtable: nil) + + public init() { + cContext.context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) + cContext.vtable = UnsafeMutablePointer(&cliVtable) + } + + // Subclasses override these as needed: + + func delim(_ opt: String? = nil) {} + + func write(_ data: Data) {} + + func puts(_ str: String) { + self.write(str.data(using:.utf8)!) + } + + func flush() {} + + func putString(_ str: String, _ delim: String? = nil) { + self.puts(str) + self.delim(delim) + } + + func putLong(_ value: Int64, _ delim: String? = nil) { + self.puts("\(value)") + self.delim(delim) + } + + func putHexData(_ data: Data, _ delim: String? = nil) { + self.puts(data.hexUpper) + self.delim(delim) + } + + func putBlob(_ data: Data, _ delim: String? = nil) { + self.write(data) + self.delim(delim) + } + + func startTable(column_names: [String]) { + self.putLong(Int64(column_names.count)) + for i in 0 ..< column_names.count { + self.putString(column_names[i], i == column_names.count - 1 ? nil : ":") + } + } + + func endTable(row_count: Int) {} + + func fieldName(_ name: String, _ delim: String? = nil) { + self.putString(name) + self.delim(delim) + } +} + +private func _self(_ context: UnsafeMutablePointer?) -> CliContext { + return Unmanaged.fromOpaque(context!.pointee.context).takeUnretainedValue() +} + +private func cliDelim(_ context: UnsafeMutablePointer?, _ opt: UnsafePointer?) -> Void { + _self(context).delim(opt != nil ? String(cString: opt!) : nil) +} + +private func cliWrite(_ context: UnsafeMutablePointer?, _ buf: UnsafePointer?, _ len: Int) -> Void { + if buf != nil { + _self(context).write(Data(bytes: buf!, count: len)) + } +} + +private func cliPuts(_ context: UnsafeMutablePointer?, _ str: UnsafePointer?) -> Void { + if str != nil { + _self(context).putString(String(cString: str!)) + } +} + +private func cliVprintf(_ context: UnsafeMutablePointer?, _ fmt: UnsafePointer?, _ ap: CVaListPointer) -> Void { + let str = NSString(format: String(cString: fmt!), arguments: ap) + _self(context).putString(String(data: str.data(using: String.Encoding.utf16.rawValue)!, encoding:.utf16)!) +} + +private func cliPutLong(_ context: UnsafeMutablePointer?, _ value: Int64, _ delim: UnsafePointer?) -> Void { + _self(context).putLong(value, delim != nil ? String(cString: delim!) : nil) +} + +private func cliPutString(_ context: UnsafeMutablePointer?, _ value: UnsafePointer?, _ delim: UnsafePointer?) -> Void { + _self(context).putString(value != nil ? String(cString: value!) : "", + delim != nil ? String(cString: delim!) : nil) +} + +private func cliPutHexvalue(_ context: UnsafeMutablePointer?, _ buf: UnsafePointer?, _ len: Int, _ delim: UnsafePointer?) -> Void { + _self(context).putHexData(buf != nil ? Data(bytes: buf!, count: len) : Data(), + delim != nil ? String(cString: delim!) : nil) +} + +private func cliPutBlob(_ context: UnsafeMutablePointer?, _ blob: UnsafePointer?, _ len: Int, _ delim: UnsafePointer?) -> Void { + _self(context).putBlob(blob != nil ? Data(bytes: UnsafeRawPointer(blob!).bindMemory(to: CChar.self, capacity: len), count: len) : Data(), + delim != nil ? String(cString: delim!) : nil) +} + +private func cliStartTable(_ context: UnsafeMutablePointer?, _ column_count: Int, _ column_names: UnsafeMutablePointer?>?) -> Void { + let names = (0..?, _ row_count: Int) -> Void { + _self(context).endTable(row_count: row_count) +} + +private func cliFieldName(_ context: UnsafeMutablePointer?, _ name: UnsafePointer?, _ delim: UnsafePointer?) -> Void { + _self(context).fieldName(name != nil ? String(cString: name!) : "", delim != nil ? String(cString: delim!) : nil) +} + +private func cliFlush(_ context: UnsafeMutablePointer?) -> Void { + _self(context).flush() +} + +private var cliVtable = cli_vtable( + delim: cliDelim, + write: cliWrite, + puts: cliPuts, + vprintf: cliVprintf, + put_long: cliPutLong, + put_string: cliPutString, + put_hexvalue: cliPutHexvalue, + put_blob: cliPutBlob, + start_table: cliStartTable, + end_table: cliEndTable, + field_name: cliFieldName, + flush: cliFlush + ) diff --git a/swift-daemon-api/Sources/CliContextFile.swift b/swift-daemon-api/Sources/CliContextFile.swift new file mode 100644 index 00000000..af3e6320 --- /dev/null +++ b/swift-daemon-api/Sources/CliContextFile.swift @@ -0,0 +1,54 @@ +/* +Serval DNA Swift CLI output to stdio stream +Copyright 2017 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. +*/ + +import Foundation + +/* An instance of struct cli_vtable that prints all fields to a stdio stream + * via a buffer. This is the Swift equivalent of cli_stdio.c. It is used by + * the 'servaldswift' daemon to produce its output. + */ + +open class CliContextFile: CliContext { + private var fileHandle: FileHandle + private var buffer: Data + private let bufsize: Int? = nil + + public init(_ file: FileHandle) { + self.fileHandle = file + self.buffer = Data() + } + + public override func delim(_ opt: String?) { + self.puts(ProcessInfo.processInfo.environment["SERVALD_OUTPUT_DELIMITER"] ?? opt ?? "\n") + } + + public override func write(_ data: Data) { + self.buffer.append(data) + if self.bufsize != nil && self.buffer.count >= self.bufsize! { + self.flush() + } + } + + public override func flush() { + if !self.buffer.isEmpty { + self.fileHandle.write(self.buffer) + self.buffer.removeAll() + } + } +} diff --git a/swift-daemon-api/Sources/commandline.swift b/swift-daemon-api/Sources/commandline.swift new file mode 100644 index 00000000..502bbc73 --- /dev/null +++ b/swift-daemon-api/Sources/commandline.swift @@ -0,0 +1,39 @@ +/* +Serval DNA Swift command-line entry point +Copyright 2017 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. +*/ + +import Foundation +import servald.cli + +/* A Swift entry point to the Serval DNA daemon command-line entry point, which takes a + * [String] parameter, converts it into an argv array of C strings, and invokes + * the C main entry point with argv0, and argc/argv arguments. + */ +public func serval_commandline_main(context: CliContext, args: [String]) -> CInt { + var margv = args.map { strdup($0) } + margv.append(nil) + defer { + margv.forEach { free($0) } + } + let argv0 = margv[0] + margv.remove(at: 0) + let argv = margv.map { $0 != nil ? UnsafePointer?($0!) : nil } + return argv.withUnsafeBufferPointer { + return commandline_main(&context.cContext, argv0, CInt(argv.count - 1), $0.baseAddress!) + } +} diff --git a/swift-daemon-api/Sources/log.swift b/swift-daemon-api/Sources/log.swift new file mode 100644 index 00000000..ee0765b9 --- /dev/null +++ b/swift-daemon-api/Sources/log.swift @@ -0,0 +1,39 @@ +import servald.log + +private func serval_log(level: CInt, format: String, va_list: CVaListPointer) { + format.withCString { CString in + serval_vlogf(level, __whence, CString, va_list) + } +} + +public func serval_log(level: CInt, text: String) { + text.withCString { CString in + withVaList([CString]) { va_list in + serval_log(level: level, format: "%s", va_list: va_list) + } + } +} + +public func serval_log_fatal(_ text: String) { + serval_log(level: LOG_LEVEL_FATAL, text: text) +} + +public func serval_log_error(_ text: String) { + serval_log(level: LOG_LEVEL_ERROR, text: text) +} + +public func serval_log_warning(_ text: String) { + serval_log(level: LOG_LEVEL_WARN, text: text) +} + +public func serval_log_hint(_ text: String) { + serval_log(level: LOG_LEVEL_HINT, text: text) +} + +public func serval_log_info(_ text: String) { + serval_log(level: LOG_LEVEL_INFO, text: text) +} + +public func serval_log_debug(_ text: String) { + serval_log(level: LOG_LEVEL_DEBUG, text: text) +}