diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e7578124..1d26cb11 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -12,6 +12,7 @@ libc = "0.2" glib-sys = "0.6.0" gio-sys = "0.6.0" glib = "0.5.0" +tempfile = "3.0.3" [lib] name = "rpmostree_rust" diff --git a/rust/include/rpmostree-rust.h b/rust/include/rpmostree-rust.h index 54388ce3..7a13019b 100644 --- a/rust/include/rpmostree-rust.h +++ b/rust/include/rpmostree-rust.h @@ -20,6 +20,13 @@ #pragma once -int rpmostree_rs_treefile_read (const char *filename, - const char *arch, - int output_fd, GError **error); +typedef struct RpmOstreeRsTreefile RpmOstreeRsTreefile; + +RpmOstreeRsTreefile *rpmostree_rs_treefile_new (const char *filename, + const char *arch, + GError **error); + +int rpmostree_rs_treefile_to_json (RpmOstreeRsTreefile *tf, GError **error); + +void rpmostree_rs_treefile_free (RpmOstreeRsTreefile *tf); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(RpmOstreeRsTreefile, rpmostree_rs_treefile_free); diff --git a/rust/src/glibutils.rs b/rust/src/glibutils.rs index 6b4c7e6a..8515a737 100644 --- a/rust/src/glibutils.rs +++ b/rust/src/glibutils.rs @@ -34,7 +34,7 @@ use std::ptr; // return a Result (using the std Error). // TODO: Try upstreaming this into the glib crate? -fn error_to_glib(e: &Error, gerror: *mut *mut glib_sys::GError) { +pub fn error_to_glib(e: &Error, gerror: *mut *mut glib_sys::GError) { if gerror.is_null() { return; } @@ -49,6 +49,7 @@ fn error_to_glib(e: &Error, gerror: *mut *mut glib_sys::GError) { } } +#[allow(dead_code)] pub fn int_glib_error(res: Result, gerror: *mut *mut glib_sys::GError) -> libc::c_int where E: std::error::Error, diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 3028fb23..2cbe9880 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -20,6 +20,7 @@ extern crate gio_sys; extern crate glib; extern crate glib_sys; extern crate libc; +extern crate tempfile; #[macro_use] extern crate serde_derive; @@ -29,13 +30,12 @@ extern crate serde_yaml; use std::ffi::{CStr, OsStr}; use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::{FromRawFd, IntoRawFd}; -use std::{fs, io}; +use std::os::unix::io::IntoRawFd; mod glibutils; use glibutils::*; mod treefile; -use treefile::treefile_read_impl; +use treefile::*; /* Wrapper functions for translating from C to Rust */ @@ -58,23 +58,41 @@ fn bytes_from_nonnull<'a>(s: *const libc::c_char) -> &'a [u8] { } #[no_mangle] -pub extern "C" fn rpmostree_rs_treefile_read( +pub extern "C" fn rpmostree_rs_treefile_new( filename: *const libc::c_char, arch: *const libc::c_char, - fd: libc::c_int, error: *mut *mut glib_sys::GError, -) -> libc::c_int { +) -> *mut Treefile { // Convert arguments let filename = OsStr::from_bytes(bytes_from_nonnull(filename)); let arch = str_from_nullable(arch); - // Using an O_TMPFILE is an easy way to avoid ownership transfer issues w/ - // returning allocated memory across the Rust/C boundary; the dance with - // `file` is to avoid dup()ing the fd unnecessarily. - let file = unsafe { fs::File::from_raw_fd(fd) }; - let r = { - let output = io::BufWriter::new(&file); - int_glib_error(treefile_read_impl(filename.as_ref(), arch, output), error) - }; - file.into_raw_fd(); // Drop ownership of the FD again - r + // Run code, map error if any, otherwise extract raw pointer, passing + // ownership back to C. + ptr_glib_error(Treefile::new_boxed(filename.as_ref(), arch), error) +} + +#[no_mangle] +pub extern "C" fn rpmostree_rs_treefile_to_json( + tf: *mut Treefile, + gerror: *mut *mut glib_sys::GError, +) -> libc::c_int { + assert!(!tf.is_null()); + let tf = unsafe { &mut *tf }; + match tf.serialize_json_fd() { + Ok(f) => f.into_raw_fd() as libc::c_int, + Err(e) => { + error_to_glib(&e, gerror); + -1 as libc::c_int + } + } +} + +#[no_mangle] +pub extern "C" fn rpmostree_rs_treefile_free(tf: *mut Treefile) { + if tf.is_null() { + return; + } + unsafe { + Box::from_raw(tf); + } } diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index b04a82c7..7c9c7144 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -22,12 +22,17 @@ use serde_json; use serde_yaml; - +use std::io::prelude::*; use std::path::Path; use std::{fs, io}; +use tempfile; const ARCH_X86_64: &'static str = "x86_64"; +pub struct Treefile { + pub parsed: TreeComposeConfig, +} + /// Parse a YAML treefile definition using architecture `arch`. fn treefile_parse_yaml(input: R, arch: Option<&str>) -> io::Result { let mut treefile: TreeComposeConfig = match serde_yaml::from_reader(input) { @@ -77,15 +82,22 @@ fn treefile_parse_yaml(input: R, arch: Option<&str>) -> io::Result< Ok(treefile) } -pub fn treefile_read_impl( - filename: &Path, - arch: Option<&str>, - output: W, -) -> io::Result<()> { - let f = io::BufReader::new(fs::File::open(filename)?); - let treefile = treefile_parse_yaml(f, arch)?; - serde_json::to_writer_pretty(output, &treefile)?; - Ok(()) +impl Treefile { + pub fn new_boxed(filename: &Path, arch: Option<&str>) -> io::Result> { + let f = io::BufReader::new(fs::File::open(filename)?); + let parsed = treefile_parse_yaml(f, arch)?; + Ok(Box::new(Treefile { parsed: parsed })) + } + + pub fn serialize_json_fd(&self) -> io::Result { + let mut tmpf = tempfile::tempfile()?; + { + let output = io::BufWriter::new(&tmpf); + serde_json::to_writer_pretty(output, &self.parsed)?; + } + tmpf.seek(io::SeekFrom::Start(0))?; + Ok(tmpf) + } } fn whitespace_split_packages(pkgs: &[String]) -> Vec { diff --git a/src/app/rpmostree-compose-builtin-tree.c b/src/app/rpmostree-compose-builtin-tree.c index 5fccee7c..61396165 100644 --- a/src/app/rpmostree-compose-builtin-tree.c +++ b/src/app/rpmostree-compose-builtin-tree.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -125,6 +126,9 @@ typedef struct { char *rojig_spec; char *previous_checksum; +#ifdef HAVE_RUST + RpmOstreeRsTreefile *treefile_rs; +#endif JsonParser *treefile_parser; JsonNode *treefile_rootval; /* Unowned */ JsonObject *treefile; /* Unowned */ @@ -155,6 +159,9 @@ rpm_ostree_tree_compose_context_free (RpmOstreeTreeComposeContext *ctx) g_free (ctx->ref); g_free (ctx->rojig_spec); g_free (ctx->previous_checksum); +#ifdef HAVE_RUST + g_clear_pointer (&ctx->treefile_rs, (GDestroyNotify) rpmostree_rs_treefile_free); +#endif g_clear_object (&ctx->treefile_parser); g_clear_pointer (&ctx->serialized_treefile, (GDestroyNotify)g_bytes_unref); g_free (ctx); @@ -682,11 +689,7 @@ parse_treefile_to_json (RpmOstreeTreeComposeContext *self, JsonParser **out_parser, GError **error) { -#ifdef HAVE_RUST - g_autofree char *fdpath = NULL; - g_auto(GLnxTmpfile) json_contents = { 0, }; -#endif - + g_autoptr(JsonParser) parser = json_parser_new (); if (g_str_has_suffix (treefile_path, ".yaml") || g_str_has_suffix (treefile_path, ".yml")) { @@ -694,22 +697,25 @@ parse_treefile_to_json (RpmOstreeTreeComposeContext *self, return glnx_throw (error, "This version of rpm-ostree was built without " "rust, and doesn't support YAML treefiles"); #else - if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &json_contents, error)) - return FALSE; - const char *arch = self ? dnf_context_get_base_arch (rpmostree_context_get_dnf (self->corectx)) : NULL; - if (!rpmostree_rs_treefile_read (treefile_path, arch, - json_contents.fd, error)) + self->treefile_rs = rpmostree_rs_treefile_new (treefile_path, arch, error); + if (!self->treefile_rs) return glnx_prefix_error (error, "Failed to load YAML treefile"); - /* or just lseek back to 0 and use json_parser_load_from_data here? */ - treefile_path = fdpath = g_strdup_printf ("/proc/self/fd/%d", json_contents.fd); + glnx_fd_close int json_fd = rpmostree_rs_treefile_to_json (self->treefile_rs, error); + if (json_fd < 0) + return FALSE; + g_autoptr(GInputStream) json_s = g_unix_input_stream_new (json_fd, FALSE); + + if (!json_parser_load_from_stream (parser, json_s, NULL, error)) + return FALSE; #endif } - - g_autoptr(JsonParser) parser = json_parser_new (); - if (!json_parser_load_from_file (parser, treefile_path, error)) - return FALSE; + else + { + if (!json_parser_load_from_file (parser, treefile_path, error)) + return FALSE; + } *out_parser = g_steal_pointer (&parser); return TRUE;