Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite archive.c in Rust #1

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config
@@ -0,0 +1,2 @@
[build]
rustflags = ["--emit=obj"]
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions Cargo.toml
@@ -0,0 +1,15 @@
[package]
name = "postgres_rust"
version = "0.0.1"
edition = "2021"

[lib]
name = "postgres_rust"
crate-type = ["staticlib"]

[dependencies]
libc = "*"

[features]
default = []
pg_rewind = []
3 changes: 2 additions & 1 deletion src/backend/Makefile
Expand Up @@ -64,7 +64,8 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)

postgres: $(OBJS)
$(CC) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(LIBS) -o $@
cargo build --release
$(CC) $(CFLAGS) $(call expand_subsys,$^) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(LIBS) ../../target/release/libpostgres_rust.a -o $@

endif
endif
Expand Down
3 changes: 2 additions & 1 deletion src/bin/pg_rewind/Makefile
Expand Up @@ -35,7 +35,8 @@ EXTRA_CLEAN = xlogreader.c
all: pg_rewind

pg_rewind: $(OBJS) | submake-libpq submake-libpgport
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
cargo build --release --features pg_rewind
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) ../../../target/release/libpostgres_rust.a

xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
rm -f $@ && $(LN_S) $< .
Expand Down
1 change: 0 additions & 1 deletion src/common/Makefile
Expand Up @@ -46,7 +46,6 @@ LIBS += $(PTHREAD_LIBS)
# If you add objects here, see also src/tools/msvc/Mkvcbuild.pm

OBJS_COMMON = \
archive.o \
base64.o \
checksum_helper.o \
compression.o \
Expand Down
103 changes: 103 additions & 0 deletions src/common/archive.rs
@@ -0,0 +1,103 @@
use libc::c_char;

use std::ffi::CString;
use std::path::Path;

use crate::port::path::make_native_path;
use crate::safe_cstr;

/// BuildRestoreCommand
///
/// Builds a restore command to retrieve a file from WAL archives, replacing
/// the supported aliases with values supplied by the caller as defined by
/// the GUC parameter restore_command: xlogpath for %p, xlogfname for %f and
/// lastRestartPointFname for %r.
///
/// The result is a palloc'd string for the restore command built. The
/// caller is responsible for freeing it. If any of the required arguments
/// is NULL and that the corresponding alias is found in the command given
/// by the caller, then NULL is returned.
#[no_mangle]
#[allow(non_snake_case)]
extern "C" fn BuildRestoreCommand(
restore_command: *const c_char,
xlog_path: *const c_char,
xlog_fname: *const c_char,
last_restart_point_fname: *const c_char,
) -> *const c_char {
match build_restore_command(
&safe_cstr(restore_command).unwrap_or(String::from("")),
Path::new(&safe_cstr(xlog_path).unwrap_or(String::from(""))),
&safe_cstr(xlog_fname).unwrap_or(String::from("")),
&safe_cstr(last_restart_point_fname).unwrap_or(String::from("")),
) {
Some(restore_command) => {
let restore_command = CString::new(restore_command).unwrap();
restore_command.as_ptr()
}

None => std::ptr::null(),
}
}

pub fn build_restore_command(
restore_command: &str,
xlog_path: &Path,
xlog_fname: &str,
last_restart_point_fname: &str,
) -> Option<String> {
let command = restore_command.to_string();
let xlog_path = make_native_path(xlog_path);

if command.contains("%p") && xlog_path.to_str().unwrap().is_empty() {
None
} else if command.contains("%f") && xlog_fname.is_empty() {
None
} else if command.contains("%r") && last_restart_point_fname.is_empty() {
None
} else {
Some(
command
.replace("%p", xlog_path.to_str().unwrap())
.replace("%f", xlog_fname)
.replace("%r", last_restart_point_fname)
.replace("%%", "%"),
)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_build_restore_command() {
let restore_command = "pgbackrest restore --stanza main \"%p\" %f";

let restore_command = build_restore_command(
restore_command,
&Path::new("/var/lib/postgresql/14/main/pg_wal"),
"0123456",
"",
)
.unwrap();

assert_eq!(
restore_command,
"pgbackrest restore --stanza main \"/var/lib/postgresql/14/main/pg_wal\" 0123456"
);
}

#[test]
fn test_build_restore_command_no_xlog_path() {
let restore_command = "pgbackrest restore --stanza main \"%p\" %f";
let restore_command = build_restore_command(
restore_command,
&Path::new("/var/lib/postgresql/14/main/pg_wal"),
"",
"",
);

assert_eq!(restore_command, None);
}
}
6 changes: 6 additions & 0 deletions src/common/logging.rs
@@ -0,0 +1,6 @@
use std::process;

pub fn pg_fatal(line: &str) -> ! {
println!("{}", line);
process::exit(1)
}
2 changes: 2 additions & 0 deletions src/common/mod.rs
@@ -0,0 +1,2 @@
pub mod archive;
pub mod logging;
1 change: 0 additions & 1 deletion src/fe_utils/Makefile
Expand Up @@ -20,7 +20,6 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)

OBJS = \
archive.o \
cancel.o \
conditional.o \
connect_utils.o \
Expand Down
90 changes: 90 additions & 0 deletions src/fe_utils/archive.rs
@@ -0,0 +1,90 @@
extern crate libc;

#[cfg(feature = "pg_rewind")]
use libc::c_char;
#[cfg(feature = "pg_rewind")]
use std::ffi::CStr;

use std::fs;
#[cfg(feature = "pg_rewind")]
use std::os::unix::io::IntoRawFd;
use std::path::Path;
use std::process;

use crate::common::archive::build_restore_command;
use crate::common::logging::pg_fatal;
use crate::include::access::xlog_internal::XLOGDIR;

pub fn restore_archived_file(
path: &str,
xlog_fname: &str,
expected_size: u64,
restore_command: &str,
) -> fs::File {
let xlog_path = format!("{}/{}/{}", XLOGDIR, path, xlog_fname);
let xlog_path = Path::new(&xlog_path);
let xlog_restore_command =
match build_restore_command(restore_command, &xlog_path, xlog_fname, "") {
Some(xlog_restore_command) => xlog_restore_command,
None => pg_fatal("cannot use restore_command with %r placeholder"),
};

// Run command
match process::Command::new(xlog_restore_command).output() {
Ok(output) => match output.status.code() {
Some(0) => (),
Some(_) => pg_fatal(&format!(
"could not restore file \"{}\" from archive",
xlog_path.display()
)),
None => pg_fatal("restore command failed: terminated by signal"),
},
Err(err) => pg_fatal(&format!("restore command failed: {:?}", err)),
};

match fs::metadata(&xlog_path) {
Ok(metadata) => {
if metadata.len() != expected_size {
pg_fatal(&format!(
"unexpected file size for \"{}\": {} instead of {}",
xlog_path.display(),
metadata.len(),
expected_size
));
}
}
Err(err) => pg_fatal(&format!(
"could not stat file \"{}\": {:?}",
xlog_path.display(),
err
)),
};

match fs::File::open(&xlog_path) {
Ok(file) => file,
Err(err) => pg_fatal(&format!(
"could not open file \"{}\" restored from archive: {:?}",
xlog_path.display(),
err
)),
}
}

#[cfg(feature = "pg_rewind")]
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn RestoreArchivedFile(
path: *const c_char,
xlog_fname: *const c_char,
expected_size: libc::size_t,
restore_command: *const c_char,
) -> libc::c_int {
let file = restore_archived_file(
unsafe { CStr::from_ptr(path).to_str().unwrap() },
unsafe { CStr::from_ptr(xlog_fname).to_str().unwrap() },
expected_size.try_into().unwrap(),
unsafe { CStr::from_ptr(restore_command).to_str().unwrap() },
);

file.into_raw_fd()
}
1 change: 1 addition & 0 deletions src/fe_utils/mod.rs
@@ -0,0 +1 @@
pub mod archive;
1 change: 1 addition & 0 deletions src/include/access/mod.rs
@@ -0,0 +1 @@
pub mod xlog_internal;
1 change: 1 addition & 0 deletions src/include/access/xlog_internal.rs
@@ -0,0 +1 @@
pub const XLOGDIR: &'static str = "pg_wal";
1 change: 1 addition & 0 deletions src/include/mod.rs
@@ -0,0 +1 @@
pub mod access;
18 changes: 18 additions & 0 deletions src/lib.rs
@@ -0,0 +1,18 @@
use libc::c_char;
use std::ffi::CStr;

pub mod common;
pub mod fe_utils;
pub mod include;
pub mod port;

/// Create an owned String from a possilby NULL C-string.
pub fn safe_cstr(ptr: *const c_char) -> Option<String> {
unsafe {
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr).to_str().unwrap().to_owned())
}
}
}
1 change: 1 addition & 0 deletions src/port/mod.rs
@@ -0,0 +1 @@
pub mod path;
24 changes: 24 additions & 0 deletions src/port/path.rs
@@ -0,0 +1,24 @@
use std::path::{Path, PathBuf};

/// make_native_path - on WIN32, change / to \ in the path
///
/// This effectively undoes canonicalize_path.
///
/// This is required because WIN32 COPY is an internal CMD.EXE
/// command and doesn't process forward slashes in the same way
/// as external commands. Quoting the first argument to COPY
/// does not convert forward to backward slashes, but COPY does
/// properly process quoted forward slashes in the second argument.
///
/// COPY works with quoted forward slashes in the first argument
/// only if the current directory is the same as the directory
/// of the first argument.
pub fn make_native_path(path: &Path) -> PathBuf {
if cfg!(windows) {
// Windows strings are UTF...right?
let path = path.to_str().unwrap_or("");
Path::new(&path.replace("/", "\\")).to_owned()
} else {
path.to_owned()
}
}