Skip to content

Commit f580ef4

Browse files
committed
refactor: moved OsStr->CString->ptr utilities to a separate module
1 parent 1d3dd69 commit f580ef4

5 files changed

Lines changed: 94 additions & 56 deletions

File tree

src/conv.rs

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
//! convrnx(StreamFmt::Rinex, &mut opt, "input.rnx", &ofiles).unwrap();
99
//! ```
1010
11-
use crate::{GpsTime, NavSys};
11+
use crate::{
12+
util::{copy_osstr, CStringArray},
13+
GpsTime, NavSys,
14+
};
1215
use num_enum::TryFromPrimitive;
1316
use rtklib_sys::rtklib as ffi;
14-
use std::ffi::{CString, OsStr, OsString};
15-
use std::os::unix::ffi::OsStrExt;
17+
use std::ffi::{OsStr, OsString};
1618
use thiserror::Error;
1719

1820
/// Input stream format for [`convrnx`].
@@ -133,15 +135,6 @@ pub enum RinexVersion {
133135
V305 = 305,
134136
}
135137

136-
fn copy_osstr<const N: usize>(dst: &mut [i8; N], src: &OsStr) {
137-
let src = src.as_bytes();
138-
let n = src.len().min(N - 1);
139-
unsafe {
140-
std::ptr::copy_nonoverlapping(src.as_ptr() as *const i8, dst.as_mut_ptr(), n);
141-
dst[n] = 0;
142-
}
143-
}
144-
145138
/// RINEX conversion options.
146139
#[derive(Clone, Copy)]
147140
pub struct RnxOpt(ffi::rnxopt_t);
@@ -446,23 +439,15 @@ pub fn convrnx(
446439
file: impl AsRef<OsStr>,
447440
ofile: &RnxOutputFiles,
448441
) -> Result<(), ConvrnxError> {
449-
let file = file.as_ref();
450-
let file_c =
451-
CString::new(file.as_bytes()).map_err(|_| ConvrnxError::NulByte(file.to_owned()))?;
452-
453-
let mut cstrings = ofile
454-
.as_slice()
455-
.iter()
456-
.map(|p| CString::new(p.as_bytes()).map_err(|_| ConvrnxError::NulByte(p.clone())))
457-
.collect::<Result<Vec<_>, _>>()?;
458-
let mut ptrs: Vec<*mut i8> = cstrings.iter().map(|s| s.as_ptr() as *mut i8).collect();
442+
let file_arr = CStringArray::try_single(file.as_ref()).map_err(|e| ConvrnxError::NulByte(e.to_owned()))?;
443+
let mut ofile_arr = CStringArray::try_new(ofile.as_slice()).map_err(|e| ConvrnxError::NulByte(e.to_owned()))?;
459444

460445
let ret = unsafe {
461446
ffi::convrnx(
462447
format as i32,
463448
&mut opt.0,
464-
file_c.as_ptr(),
465-
ptrs.as_mut_ptr(),
449+
file_arr.first(),
450+
ofile_arr.as_mut_ptr() as *mut *mut i8,
466451
)
467452
};
468453

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub use solution::*;
3737
pub mod meas;
3838
pub use meas::*;
3939

40+
mod util;
41+
4042
/// Error returned when a decoder fails to initialize.
4143
#[derive(Debug, thiserror::Error)]
4244
#[error("failed to initialize decoder")]

src/ppk.rs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! ).unwrap();
1515
//! ```
1616
17-
use crate::NavSys;
17+
use crate::{util::CStringArray, NavSys};
1818
use num_enum::TryFromPrimitive;
1919
use rtklib_sys::rtklib as ffi;
2020
use std::ffi::CString;
@@ -427,26 +427,22 @@ pub fn postpos(
427427
});
428428
}
429429

430-
let mut cstrings = Vec::with_capacity(total);
431-
let mut paths = vec![rover_obs, base_obs];
432-
paths.extend_from_slice(nav_files);
433-
434-
for p in &paths {
435-
cstrings.push(CString::new(*p).map_err(|_| PostposError::NulByte(p.to_string()))?);
436-
}
430+
let mut all_inputs = vec![rover_obs, base_obs];
431+
all_inputs.extend_from_slice(nav_files);
437432

438433
// C signature is `const char **infile` — the strings are const but the
439434
// pointer to the array is not, so bindgen generates `*mut *const c_char`.
440435
// The function does not actually mutate the array.
441-
let mut ptrs: Vec<*const i8> = cstrings.iter().map(|s| s.as_ptr()).collect();
442-
let outfile = CString::new(output).map_err(|_| PostposError::NulByte(output.to_string()))?;
436+
let mut infile_arr = CStringArray::try_new(&all_inputs)
437+
.map_err(|e| PostposError::NulByte(e.to_string_lossy().into_owned()))?;
438+
let out_arr = CStringArray::try_new(&[output])
439+
.map_err(|e| PostposError::NulByte(e.to_string_lossy().into_owned()))?;
440+
let rov = CString::new("").unwrap();
441+
let base = CString::new("").unwrap();
443442

444443
let ts = ffi::gtime_t { time: 0, sec: 0.0 };
445444
let te = ffi::gtime_t { time: 0, sec: 0.0 };
446445

447-
let rov = CString::new("").unwrap();
448-
let base = CString::new("").unwrap();
449-
450446
let ret = unsafe {
451447
ffi::postpos(
452448
ts,
@@ -456,9 +452,9 @@ pub fn postpos(
456452
popt.as_ffi(),
457453
sopt.as_ffi(),
458454
fopt.as_ffi(),
459-
ptrs.as_mut_ptr(),
460-
ptrs.len() as i32,
461-
outfile.as_ptr(),
455+
infile_arr.as_mut_ptr(),
456+
infile_arr.len() as i32,
457+
out_arr.first(),
462458
rov.as_ptr(),
463459
base.as_ptr(),
464460
)

src/solution.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
//! [`read_solt`] wraps `readsolt`, and [`write_sol`] wraps `outsolheads` and
55
//! `outsols` from `solution.c`.
66
7-
use crate::{ppk::SolOpt, GpsTime, Llh, SolStatus};
7+
use crate::{ppk::SolOpt, util::CStringArray, GpsTime, Llh, SolStatus};
88
use num_enum::TryFromPrimitive;
99
use rtklib_sys::rtklib as ffi;
1010
use std::{
11-
ffi::CString,
1211
fs::File,
1312
io::{Error as IoError, Write},
1413
};
@@ -195,20 +194,12 @@ fn validate_coord_types(buf: SolBuf) -> Result<SolBuf, SolError> {
195194
Ok(buf)
196195
}
197196

198-
fn build_cstring_ptrs(paths: &[&str]) -> Result<(Vec<CString>, Vec<*const i8>), SolError> {
199-
let cstrings = paths
200-
.iter()
201-
.map(|p| CString::new(*p).map_err(|_| SolError::NulByte(p.to_string())))
202-
.collect::<Result<Vec<_>, _>>()?;
203-
let ptrs = cstrings.iter().map(|s| s.as_ptr()).collect();
204-
Ok((cstrings, ptrs))
205-
}
206-
207197
/// Read solution records from one or more `.pos` files.
208198
pub fn read_sol(paths: &[&str]) -> Result<SolBuf, SolError> {
209-
let (_cstrings, mut ptrs) = build_cstring_ptrs(paths)?;
199+
let mut arr = CStringArray::try_new(paths)
200+
.map_err(|e| SolError::NulByte(e.to_string_lossy().into_owned()))?;
210201
let mut buf = unsafe { std::mem::zeroed::<ffi::solbuf_t>() };
211-
let ret = unsafe { ffi::readsol(ptrs.as_mut_ptr(), ptrs.len() as i32, &mut buf) };
202+
let ret = unsafe { ffi::readsol(arr.as_mut_ptr(), arr.len() as i32, &mut buf) };
212203
if ret == 0 {
213204
return Err(SolError::ReadFailed);
214205
}
@@ -224,12 +215,13 @@ pub fn read_solt(
224215
qflag: i32,
225216
mean: bool,
226217
) -> Result<SolBuf, SolError> {
227-
let (_cstrings, mut ptrs) = build_cstring_ptrs(paths)?;
218+
let mut arr = CStringArray::try_new(paths)
219+
.map_err(|e| SolError::NulByte(e.to_string_lossy().into_owned()))?;
228220
let mut buf = unsafe { std::mem::zeroed::<ffi::solbuf_t>() };
229221
let ret = unsafe {
230222
ffi::readsolt(
231-
ptrs.as_mut_ptr(),
232-
ptrs.len() as i32,
223+
arr.as_mut_ptr(),
224+
arr.len() as i32,
233225
ts.0,
234226
te.0,
235227
tint,

src/util.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::{
2+
ffi::{CString, OsStr},
3+
os::unix::ffi::OsStrExt,
4+
};
5+
6+
/// Copy an `OsStr` into a fixed-size null-terminated `[i8; N]` C buffer.
7+
/// Truncates silently if `src` is longer than `N - 1` bytes.
8+
pub(crate) fn copy_osstr<const N: usize>(dst: &mut [i8; N], src: &OsStr) {
9+
let src = src.as_bytes();
10+
let n = src.len().min(N - 1);
11+
unsafe {
12+
std::ptr::copy_nonoverlapping(src.as_ptr() as *const i8, dst.as_mut_ptr(), n);
13+
dst[n] = 0;
14+
}
15+
}
16+
17+
/// Owned array of C strings with a stable pointer list for FFI calls.
18+
///
19+
/// `as_ptr` holds pointers into each `CString`'s owned string buffer. Moving this
20+
/// struct retains the validity of the pointers to the heap-allocated strings.
21+
pub(crate) struct CStringArray {
22+
_strings: Vec<CString>,
23+
ptrs: Vec<*const i8>,
24+
}
25+
26+
impl CStringArray {
27+
/// Build from a slice of paths. Returns the offending path if one contains a NUL byte.
28+
pub(crate) fn try_new<T: AsRef<OsStr>>(paths: &[T]) -> Result<Self, &OsStr> {
29+
let strings = paths
30+
.iter()
31+
.map(|p| {
32+
let os = p.as_ref();
33+
CString::new(os.as_bytes()).map_err(|_| os)
34+
})
35+
.collect::<Result<Vec<_>, _>>()?;
36+
let ptrs = strings.iter().map(|s| s.as_ptr()).collect();
37+
Ok(Self { _strings: strings, ptrs })
38+
}
39+
40+
/// Build from a single path. Returns the offending path if it contains a NUL byte.
41+
pub(crate) fn try_single(path: &OsStr) -> Result<Self, &OsStr> {
42+
let string = CString::new(path.as_bytes()).map_err(|_| path)?;
43+
Ok(Self {
44+
ptrs: vec![string.as_ptr()],
45+
_strings: vec![string],
46+
})
47+
}
48+
49+
/// Pointer to the first string. Panics if empty.
50+
pub(crate) fn first(&self) -> *const i8 {
51+
self.ptrs[0]
52+
}
53+
54+
/// Mutable pointer to the start of the pointer array, for passing to C as `char **`.
55+
pub(crate) fn as_mut_ptr(&mut self) -> *mut *const i8 {
56+
self.ptrs.as_mut_ptr()
57+
}
58+
59+
/// Number of strings in the array.
60+
pub(crate) fn len(&self) -> usize {
61+
self.ptrs.len()
62+
}
63+
}

0 commit comments

Comments
 (0)