[RFC,1/8] rust: Add rust interface for parsing JSON

Message ID 20181218041953.8960-2-sam@mendozajonas.com
State New
Headers show
Series
  • Introduce pb-plugin 'commands' & Rust PoC
Related show

Commit Message

Samuel Mendoza-Jonas Dec. 18, 2018, 4:19 a.m.
For plugin commands the command definitions are stored in a JSON file as
part of the plugin. Instead of doing even more string parsing in C the
Rust library provides an interface to the popular Serde library.
The Rust interface provides a few FFI-safe definitions to pass data back
and forth but does all the parsing itself.

The library is built statically, but some extra work is done in build.rs
to help it link into the existing Autoconf more easily. This is heavily
based on the libtool crate[0] with some improvements.

[0] https://docs.rs/libtool/0.1.1/libtool/

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
---
 Makefile.am          |   1 -
 configure.ac         |   9 ++
 lib/Makefile.am      |   9 +-
 lib/rust/Cargo.lock  | 117 ++++++++++++++++++++++
 lib/rust/Cargo.toml  |  20 ++++
 lib/rust/Makefile.am |  30 ++++++
 lib/rust/build.rs    |  70 +++++++++++++
 lib/rust/rustlibs.c  | 109 ++++++++++++++++++++
 lib/rust/rustlibs.h  |  11 +++
 lib/rust/src/lib.rs  | 230 +++++++++++++++++++++++++++++++++++++++++++
 lib/types/types.h    |  30 ++++++
 11 files changed, 634 insertions(+), 2 deletions(-)
 create mode 100644 lib/rust/Cargo.lock
 create mode 100644 lib/rust/Cargo.toml
 create mode 100644 lib/rust/Makefile.am
 create mode 100644 lib/rust/build.rs
 create mode 100644 lib/rust/rustlibs.c
 create mode 100644 lib/rust/rustlibs.h
 create mode 100644 lib/rust/src/lib.rs

Patch

diff --git a/Makefile.am b/Makefile.am
index 63456ca4..c0e5c8c2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -72,4 +72,3 @@  include ui/test/Makefile.am
 include man/Makefile.am
 
 include utils/Makefile.am
-
diff --git a/configure.ac b/configure.ac
index 4151b002..3d7e9964 100644
--- a/configure.ac
+++ b/configure.ac
@@ -450,6 +450,15 @@  esac
 
 AC_DEFINE_UNQUOTED(TFTP_TYPE, $tftp_type, [tftp client type])
 
+AC_ARG_VAR(
+	[CARGO_OPTS],
+	[Arguments to cargo]
+)
+AC_ARG_VAR(
+	[RUSTC_TARGET_NAME],
+	[Target as passed to cargo]
+)
+
 default_cflags="--std=gnu99 -g \
 	-Wall -W -Wunused -Wstrict-prototypes -Wmissing-prototypes \
 	-Wmissing-declarations -Wredundant-decls"
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 69a66c37..87fe38d6 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -12,6 +12,8 @@ 
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 
+include lib/rust/Makefile.am
+
 core_lib = lib/libpbcore.la
 
 noinst_LTLIBRARIES += $(core_lib)
@@ -70,7 +72,12 @@  lib_libpbcore_la_SOURCES = \
 	lib/efi/efivar.h \
 	lib/efi/efivar.c \
 	lib/param_list/param_list.c \
-	lib/param_list/param_list.h
+	lib/param_list/param_list.h \
+	lib/rust/rustlibs.c \
+	lib/rust/rustlibs.c
+
+lib_libpbcore_la_LIBADD = \
+	$(rust_lib)
 
 if ENABLE_MTD
 lib_libpbcore_la_SOURCES += \
diff --git a/lib/rust/Cargo.lock b/lib/rust/Cargo.lock
new file mode 100644
index 00000000..5b375927
--- /dev/null
+++ b/lib/rust/Cargo.lock
@@ -0,0 +1,117 @@ 
+[[package]]
+name = "dtoa"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "itoa"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libtool"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-traits"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustlibs"
+version = "0.1.0"
+dependencies = [
+ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libtool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "0.12.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
+"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
+"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
+"checksum libtool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f576520ad5e2848b26568964320a4073984db2a11147ac4f9c2fc7be1f93b8d2"
+"checksum num-traits 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3c2bd9b9d21e48e956b763c9f37134dc62d9e95da6edb3f672cacb6caf3cd3"
+"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
+"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
+"checksum serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "e928fecdb00fe608c96f83a012633383564e730962fc7a0b79225a6acf056798"
+"checksum serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "95f666a2356d87ce4780ea15b14b13532785579a5cad2dcba5292acc75f6efe2"
+"checksum serde_derive_internals 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc848d073be32cd982380c06587ea1d433bc1a4c4a111de07ec2286a3ddade8"
+"checksum serde_json 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "57781ed845b8e742fc2bf306aba8e3b408fe8c366b900e3769fbc39f49eb8b39"
+"checksum syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)" = "517f6da31bc53bf080b9a77b29fbd0ff8da2f5a2ebd24c73c2238274a94ac7cb"
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
diff --git a/lib/rust/Cargo.toml b/lib/rust/Cargo.toml
new file mode 100644
index 00000000..c335d1bb
--- /dev/null
+++ b/lib/rust/Cargo.toml
@@ -0,0 +1,20 @@ 
+[package]
+name = "rustlibs"
+build = "build.rs"
+version = "0.1.0"
+authors = ["Samuel Mendoza-Jonas <sam@mendozajonas.com>"]
+
+[dependencies]
+serde = "1.0"
+serde_derive = "1.0"
+serde_json = "1.0"
+libc = "0.2"
+
+[lib]
+crate-type = ["staticlib"]
+
+[build-dependencies]
+libtool = "0.1"
+
+[profile.release]
+lto = true
diff --git a/lib/rust/Makefile.am b/lib/rust/Makefile.am
new file mode 100644
index 00000000..add42ff2
--- /dev/null
+++ b/lib/rust/Makefile.am
@@ -0,0 +1,30 @@ 
+#  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; version 2 of the License.
+#
+#  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+rust_lib = librustlibs.la
+
+noinst_LTLIBRARIES += $(rust_lib)
+
+librustlibs_la_SOURCES = ""
+
+CARGO_TARGET_DIR := $(abs_top_builddir)/lib/rust
+
+$(rust_lib):
+	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) cargo build --release --manifest-path $(top_srcdir)/lib/rust/Cargo.toml $(CARGO_OPTS)
+	ln -sf $(CARGO_TARGET_DIR)/$(RUSTC_TARGET_NAME)/release/librustlibs.a
+	ln -sf $(CARGO_TARGET_DIR)/$(RUSTC_TARGET_NAME)/release/librustlibs.la
+	ln -sf $(CARGO_TARGET_DIR)/$(RUSTC_TARGET_NAME)/release/.libs
+
+clean-local:
+	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) cargo clean --manifest-path $(top_srcdir)/lib/rust/Cargo.toml
diff --git a/lib/rust/build.rs b/lib/rust/build.rs
new file mode 100644
index 00000000..513e119f
--- /dev/null
+++ b/lib/rust/build.rs
@@ -0,0 +1,70 @@ 
+/*
+ * From https://docs.rs/libtool/0.1.1/libtool/
+ * Upstream doesn't handle some of the directory structure we use in Petitboot,
+ * use a few extra environment variables to properly determine the target paths.
+ */
+
+use std::env;
+use std::fs::File;
+use std::fs;
+use std::io::prelude::*;
+use std::os::unix::fs::symlink;
+use std::path::PathBuf;
+
+/// Generate libtool archive file ${lib}.la
+pub fn generate_convenience_lib(lib: &str) -> std::io::Result<()> {
+    let self_version = env!("CARGO_PKG_VERSION");
+    let profile = env::var("PROFILE").unwrap();
+    let target_arch = env::var("TARGET").unwrap();
+    let target_dir_env = env::var("CARGO_TARGET_DIR").unwrap();
+
+    /* Check if the output directory will include the arch */
+    let target_dir = if PathBuf::from(format!("{}/{}",
+                                target_dir_env, target_arch)).exists() {
+        format!("{}/{}/{}", target_dir_env, target_arch, profile)
+    } else {
+        format!("{}/{}", target_dir_env, profile)
+    };
+
+    /* Location of original static library */
+    let old_lib_path = PathBuf::from(format!("{}/{}.a",
+                                            target_dir, lib));
+    /* Paths for new .la file and symlinks */
+    let libs_dir = format!("{}/.libs", target_dir);
+    let libs_path = PathBuf::from(&libs_dir);
+    let la_path = PathBuf::from(format!("{}/{}.la",
+                                        target_dir, lib));
+    let new_lib_path = PathBuf::from(format!("{}/{}.a", libs_dir, lib));
+
+    match fs::create_dir_all(&libs_path) {
+        Ok(()) => println!("libs_path created"),
+        _ => panic!("Failed to create libs_path"),
+    }
+
+    if la_path.exists() {
+        fs::remove_file(&la_path)?;
+    }
+
+    /* PathBuf.exists() traverses symlinks so just try and remove it */
+    match fs::remove_file(&new_lib_path) {
+        Ok(_v) => {},
+        Err(e) => println!("Error removing symlink: {:?}", e),
+    }
+
+    let mut file = File::create(&la_path)?;
+    writeln!(file, "# {}.la - a libtool library file", lib)?;
+    writeln!(file, "# Generated by libtool-rust {}", self_version)?;
+    writeln!(file, "dlname=''")?;
+    writeln!(file, "library_names=''")?;
+    writeln!(file, "old_library='{}.a'", lib)?;
+    writeln!(file, "inherited_linker_flags=' -pthread -lm -ldl'")?;
+    writeln!(file, "installed=no")?;
+    writeln!(file, "shouldnotlink=no")?;
+
+    symlink(&old_lib_path, &new_lib_path)?;
+    Ok(())
+}
+
+fn main() {
+    generate_convenience_lib("librustlibs").unwrap();
+}
diff --git a/lib/rust/rustlibs.c b/lib/rust/rustlibs.c
new file mode 100644
index 00000000..fe686bef
--- /dev/null
+++ b/lib/rust/rustlibs.c
@@ -0,0 +1,109 @@ 
+/*
+ *  Copyright (C) 2018 IBM Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  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.
+ *
+ */
+
+#include <log/log.h>
+#include <talloc/talloc.h>
+
+#include "rustlibs.h"
+
+enum field_type {
+	FIELD_STR,
+	FIELD_I64,
+	FIELD_F64,
+};
+
+/* Internal FFI Structs */
+struct ArgumentRaw {
+	char		*name;
+	enum field_type type;
+	int64_t		arg_i64;
+	double		arg_f64;
+	char		*arg_str;
+};
+
+struct CommandRaw {
+	char			*platform;
+	char			*name;
+	char			*cmd;
+	char			*args_fmt;
+	struct ArgumentRaw	*args;
+	size_t			n_args;
+	char			*help;
+};
+
+struct CommandArray {
+	struct CommandRaw	*commands;
+	size_t			len;
+};
+
+/* Provided by librustlibs */
+struct CommandArray *parse_json(const char *filename);
+void free_command_array(struct CommandArray *commands);
+
+int parse_command_file(void *ctx, const char *path, struct command **commands)
+{
+	struct CommandArray *raw_commands;
+	struct CommandRaw *tmp;
+	struct command *new;
+	struct argument *arg;
+	unsigned int i, j, len;
+
+	raw_commands = parse_json(path);
+	if (!raw_commands) {
+		pb_log("Failed to parse machine command\n");
+		return 0;
+	}
+
+	len = raw_commands->len;
+	new = talloc_zero_array(ctx, struct command, len);
+	for (i = 0; i < len; i++) {
+		tmp = &raw_commands->commands[i];
+		new[i].platform = talloc_strdup(new, tmp->platform);
+		new[i].name = talloc_strdup(new, tmp->name);
+		new[i].cmd = talloc_strdup(new, tmp->cmd);
+		new[i].args_fmt = talloc_strdup(new, tmp->args_fmt);
+
+		new[i].n_args = tmp->n_args;
+		new[i].args = talloc_zero_array(new, struct argument,
+				new[i].n_args);
+		for (j = 0; j < tmp->n_args; j++) {
+			arg = &new[i].args[j];
+			arg->name = talloc_strdup(new, tmp->args[j].name);
+			arg->type = tmp->args[j].type;
+			switch (tmp->args[j].type) {
+			case FIELD_STR:
+				arg->arg_str = talloc_strdup(new,
+						tmp->args[j].arg_str);
+				break;
+			case FIELD_I64:
+				arg->arg_i64 = tmp->args[j].arg_i64;
+				break;
+			case FIELD_F64:
+				arg->arg_f64 = tmp->args[j].arg_f64;
+				break;
+			default:
+				pb_log_fn("Unknown field type %d for arg '%s'\n",
+						tmp->args[j].type, tmp->args[j].name);
+				break;
+			}
+		}
+
+		new[i].help = talloc_strdup(new, tmp->help);
+	}
+
+	free_command_array(raw_commands);
+
+	*commands = new;
+	return len;
+}
diff --git a/lib/rust/rustlibs.h b/lib/rust/rustlibs.h
new file mode 100644
index 00000000..0246657b
--- /dev/null
+++ b/lib/rust/rustlibs.h
@@ -0,0 +1,11 @@ 
+#ifndef __RUST_LIBS__
+#define __RUST_LIBS__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <types/types.h>
+
+int parse_command_file(void *ctx, const char *path, struct command **commands);
+
+#endif /* __RUST_LIBS__ */
diff --git a/lib/rust/src/lib.rs b/lib/rust/src/lib.rs
new file mode 100644
index 00000000..5f4b484b
--- /dev/null
+++ b/lib/rust/src/lib.rs
@@ -0,0 +1,230 @@ 
+#[macro_use]
+extern crate serde_derive;
+
+extern crate serde;
+extern crate serde_json;
+extern crate libc;
+
+use std::ffi::CString;
+use std::ffi::CStr;
+use std::os::raw::c_char;
+use std::fs::File;
+use std::path::Path;
+use std::ptr;
+use std::mem;
+use libc::{c_long, c_double};
+
+/* Rust representations */
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Command {
+	platform:	CString,
+	name:		CString,
+    cmd:        CString,
+    arg_fmt:    CString,
+    args:       Vec<Argument>,
+    help:       CString,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+enum FieldType {
+    FieldString(CString),
+    // TODO Commands which display as yes/no that hide some actual command?
+    // FieldBool(Bool),
+    FieldInt(i64),
+    FieldFloat(f64),
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Argument {
+    name:           CString,
+    arg:            FieldType,
+}
+
+/* FFI respresentations */
+#[repr(C)]
+pub struct CommandArray {
+    bytes:  *mut CommandRaw,
+    len:    usize,
+}
+
+#[repr(C)]
+pub struct CommandRaw {
+    platform:   *mut c_char,
+    name:       *mut c_char,
+    cmd:        *mut c_char,
+    arg_fmt:    *mut c_char,
+    args:       *mut ArgumentRaw,
+    n_args:     usize,
+    help:       *mut c_char,
+}
+
+impl CommandRaw {
+    /*
+     * We need to convert several fields into FFI-suitable types and we
+     * don't want to 'partially forget' the Command structs, so we
+     * clone the various fields and forget them. The vec is then shrunk
+     * and forgotten so it can be passed as an array C will recognise.
+     */
+    fn new(command: &Command) -> CommandRaw {
+        CommandRaw {
+            platform: command.platform.clone().into_raw(),
+            name: command.name.clone().into_raw(),
+            cmd: command.cmd.clone().into_raw(),
+            arg_fmt: command.arg_fmt.clone().into_raw(),
+            args: CommandRaw::args_into_raw(&command.args),
+            n_args: command.args.len(),
+            help: command.help.clone().into_raw()
+        }
+    }
+
+    fn args_into_raw(args: &Vec<Argument>) -> *mut ArgumentRaw {
+
+        let mut args_raw  = Vec::<ArgumentRaw>::new();
+
+        for arg in args {
+            let raw_arg = ArgumentRaw::new(&arg);
+            args_raw.push(raw_arg);
+        }
+
+        args_raw.shrink_to_fit();
+        let ptr = args_raw.as_mut_ptr();
+        mem::forget(args_raw);
+
+        return ptr;
+    }
+}
+
+#[repr(C)]
+/*
+ * Rust enums are not C enums and Rust doesn't have a 'union' concept like in C
+ * so we just declare all the fields here and set them based on arg_type.
+ */
+pub struct ArgumentRaw {
+    name:       *mut c_char,
+    arg_type:   FieldTypeRaw,
+    arg_i64:    c_long,
+    arg_f64:    c_double,
+    arg_str:    *mut c_char,
+}
+
+impl ArgumentRaw {
+    fn new(arg: &Argument) -> ArgumentRaw {
+        let raw_arg = match arg.arg {
+            FieldType::FieldString(ref s) => {
+                ArgumentRaw {
+                    name: arg.name.clone().into_raw(),
+                    arg_type: FieldTypeRaw::RawString,
+                    arg_i64: 0,
+                    arg_f64: 0.0,
+                    arg_str: s.clone().into_raw(),
+                }
+            },
+            FieldType::FieldInt(i) => {
+                ArgumentRaw {
+                    name: arg.name.clone().into_raw(),
+                    arg_type: FieldTypeRaw::RawInt,
+                    arg_i64: i,
+                    arg_f64: 0.0,
+                    arg_str: ptr::null_mut(),
+                }
+            },
+            FieldType::FieldFloat(f) => {
+                ArgumentRaw {
+                    name: arg.name.clone().into_raw(),
+                    arg_type: FieldTypeRaw::RawFloat,
+                    arg_i64: 0,
+                    arg_f64: f,
+                    arg_str: ptr::null_mut(),
+                }
+            },
+        };
+        return raw_arg;
+    }
+}
+
+#[repr(C)]
+#[derive(PartialEq)]
+enum FieldTypeRaw {
+    RawString = 0,
+    RawInt,
+    RawFloat,
+}
+
+/*
+ * Take a CommandArray pointer from C and reclaim the CommandRaw
+ * structs and their members/
+ * We can't avoid the unsafe {} invocations since we're reading arbitrary
+ * memory, but it's contained to this function.
+ */
+#[no_mangle]
+pub extern fn free_command_array(commands: *mut CommandArray) {
+
+    let ptr = unsafe { Box::from_raw(commands) };
+    let raw_vec = unsafe { Vec::from_raw_parts(ptr.bytes,
+                                               ptr.len,
+                                               ptr.len) };
+    for raw_command in raw_vec.iter() {
+        unsafe {
+            let _platform = CString::from_raw(raw_command.platform);
+            let _name = CString::from_raw(raw_command.name);
+            let _cmd = CString::from_raw(raw_command.cmd);
+            let _arg_fmt = CString::from_raw(raw_command.arg_fmt);
+            /* n_args  */
+            let arg_vec = Vec::from_raw_parts(raw_command.args,
+                                              raw_command.n_args,
+                                              raw_command.n_args);
+            for raw_arg in arg_vec.iter() {
+                let _name = CString::from_raw(raw_arg.name);
+                if raw_arg.arg_type == FieldTypeRaw::RawString {
+                    let _arg_str = CString::from_raw(raw_arg.arg_str);
+                }
+            }
+            let _help = CString::from_raw(raw_command.help);
+        }
+    }
+
+    /* Frees once it goes out of scope */
+    println!("Rust reclaimed {} command structs", raw_vec.len());
+}
+
+#[no_mangle]
+pub extern fn parse_json(filename: *const c_char) -> *mut CommandArray {
+
+    let file: String;
+    unsafe {
+        /* Can't trust the promise of a string from C */
+        file = CStr::from_ptr(filename).to_string_lossy().into_owned();
+    }
+
+    let json_path = Path::new(&file);
+    let json_file = match File::open(json_path) {
+        Err(e) => {
+            println!("Could not open file: {}", e);
+            return ptr::null_mut();
+        }
+        Ok(f) => f,
+    };
+
+    let commands: Vec<Command> = match serde_json::from_reader(json_file) {
+        Err(e) => {
+            println!("Could not parse JSON: {}", e);
+            return ptr::null_mut();
+        }
+        Ok(c) =>  c,
+    };
+
+    /* Convert commands into a form more easily passed to C */
+    let mut ffi_vec = Vec::<CommandRaw>::new();
+    for command in commands.iter() {
+        let raw_command = CommandRaw::new(&command);
+        ffi_vec.push(raw_command);
+    }
+
+    let len = ffi_vec.len();
+    ffi_vec.shrink_to_fit();
+    let ffi_ptr = ffi_vec.as_mut_ptr();
+    mem::forget(ffi_vec);
+
+    let wrapper = Box::new(CommandArray { bytes: ffi_ptr, len: len });
+    return Box::into_raw(wrapper);
+}
diff --git a/lib/types/types.h b/lib/types/types.h
index 9d83d87d..6e5fb7d3 100644
--- a/lib/types/types.h
+++ b/lib/types/types.h
@@ -66,6 +66,32 @@  struct boot_option {
 	} type;
 };
 
+enum cmd_arg_type {
+	ARG_STR,
+	ARG_I64,
+	ARG_F64,
+};
+
+struct argument {
+	char			*name;
+	enum cmd_arg_type	type;
+	union {
+		char		*arg_str;
+		int64_t		arg_i64;
+		double		arg_f64;
+	};
+};
+
+struct command {
+	char			*platform;
+	char			*name;
+	char			*cmd;
+	char			*args_fmt;
+	unsigned int		n_args;
+	struct argument		*args;
+	char			*help;
+};
+
 struct plugin_option {
 	char		*id;
 	char		*name;
@@ -74,10 +100,14 @@  struct plugin_option {
 	char		*version;
 	char		*date;
 	char		*plugin_file;
+	char		*command_file;
 
 	unsigned int	n_executables;
 	char		**executables;
 
+	unsigned int	n_commands;
+	struct command	*commands;
+
 	void		*ui_info;
 };