diff --git a/postgres_ffi/Cargo.toml b/postgres_ffi/Cargo.toml new file mode 100644 index 0000000000..77cc5cf028 --- /dev/null +++ b/postgres_ffi/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "postgres_ffi" +version = "0.1.0" +authors = ["Heikki Linnakangas "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.19" +rand = "0.8.3" +bytes = "1.0.1" +byteorder = "1.4.3" +anyhow = "1.0" +crc32c = "0.6.0" +hex = "0.4.3" + +[build-dependencies] +bindgen = "0.53.1" diff --git a/postgres_ffi/build.rs b/postgres_ffi/build.rs new file mode 100644 index 0000000000..95903bf051 --- /dev/null +++ b/postgres_ffi/build.rs @@ -0,0 +1,38 @@ +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +fn main() { + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=pg_control_ffi.h"); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("pg_control_ffi.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + + .whitelist_type("ControlFileData") + .whitelist_var("PG_CONTROL_FILE_SIZE") + .whitelist_var("PG_CONTROLFILEDATA_OFFSETOF_CRC") + .whitelist_type("DBState") + + .clang_arg("-I../vendor/postgres/src/include") + + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/postgres_ffi/pg_control_ffi.h b/postgres_ffi/pg_control_ffi.h new file mode 100644 index 0000000000..169e66977b --- /dev/null +++ b/postgres_ffi/pg_control_ffi.h @@ -0,0 +1,4 @@ +#include "c.h" +#include "catalog/pg_control.h" + +const uint32 PG_CONTROLFILEDATA_OFFSETOF_CRC = offsetof(ControlFileData, crc); diff --git a/postgres_ffi/src/lib.rs b/postgres_ffi/src/lib.rs new file mode 100644 index 0000000000..b62114ea7d --- /dev/null +++ b/postgres_ffi/src/lib.rs @@ -0,0 +1,72 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use bytes::{Buf, Bytes, BytesMut}; + +// sizeof(ControlFileData) +const SIZEOF_CONTROLDATA: usize = std::mem::size_of::(); +const OFFSETOF_CRC: usize = PG_CONTROLFILEDATA_OFFSETOF_CRC as usize; + +impl ControlFileData { + + // Initialize an all-zeros ControlFileData struct + pub fn new() -> ControlFileData { + let controlfile: ControlFileData; + + let b = [0u8; SIZEOF_CONTROLDATA]; + controlfile = unsafe { + std::mem::transmute::<[u8; SIZEOF_CONTROLDATA], ControlFileData>(b) + }; + + return controlfile; + } +} + +pub fn decode_pg_control(buf: Bytes) -> Result { + + let mut b: [u8; SIZEOF_CONTROLDATA] = [0u8; SIZEOF_CONTROLDATA]; + buf.clone().copy_to_slice(&mut b); + + let controlfile: ControlFileData; + + // TODO: verify CRC + let mut data_without_crc: [u8; OFFSETOF_CRC] = [0u8; OFFSETOF_CRC]; + data_without_crc.copy_from_slice(&b[0..OFFSETOF_CRC]); + let expectedcrc = crc32c::crc32c(&data_without_crc); + + controlfile = unsafe { + std::mem::transmute::<[u8; SIZEOF_CONTROLDATA], ControlFileData>(b) + }; + + if expectedcrc != controlfile.crc { + anyhow::bail!("invalid CRC in control file: expected {:08X}, was {:08X}", + expectedcrc, controlfile.crc); + } + + Ok(controlfile) +} + +pub fn encode_pg_control(controlfile: ControlFileData) -> Bytes { + + let b: [u8; SIZEOF_CONTROLDATA]; + + b = unsafe { + std::mem::transmute::(controlfile) + }; + + // Recompute the CRC + let mut data_without_crc: [u8; OFFSETOF_CRC] = [0u8; OFFSETOF_CRC]; + data_without_crc.copy_from_slice(&b[0..OFFSETOF_CRC]); + let newcrc = crc32c::crc32c(&data_without_crc); + + let mut buf = BytesMut::with_capacity(PG_CONTROL_FILE_SIZE as usize); + + buf.extend_from_slice(&b[0..OFFSETOF_CRC]); + buf.extend_from_slice(&newcrc.to_ne_bytes()); + // Fill the rest of the control file with zeros. + buf.resize(PG_CONTROL_FILE_SIZE as usize, 0); + + return buf.into(); +}