I'm creating a binding to libdpkg
in Rust (mostly to learn about Rust FFI) using rust-bindgen
.
I've encountered a problem at the very beginning of the implementation.
I have the following Rust code:
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
use std::convert::TryInto;
use std::ffi::CString;
use std::os::raw::c_char;
pub struct Dpkg {
program_name: *mut c_char,
root_directory: Option<*mut c_char>,
}
impl Dpkg {
pub fn new(program_name: &str, root_directory: Option<&str>) -> Dpkg {
let dpkg = Dpkg {
program_name: CString::new(program_name).unwrap().into_raw(),
root_directory: match root_directory {
Some(dir) => Some(CString::new(dir).unwrap().into_raw()),
None => None,
},
};
unsafe {
dpkg_set_progname(dpkg.program_name);
push_error_context();
if let Some(dir) = dpkg.root_directory {
dpkg_db_set_dir(dir);
}
modstatdb_init();
modstatdb_open(modstatdb_rw_msdbrw_available_readonly);
}
dpkg
}
}
impl Drop for Dpkg {
fn drop(&mut self) {
unsafe {
pkg_db_reset();
modstatdb_done();
modstatdb_shutdown();
pop_error_context(ehflag_normaltidy.try_into().unwrap());
let _ = CString::from_raw(self.program_name);
if let Some(dir) = self.root_directory {
let _ = CString::from_raw(dir);
}
}
}
}
I have the following integration tests:
extern crate dpkg;
use dpkg::Dpkg;
use std::mem;
#[test]
fn test_new() {
let dpkg = Dpkg::new("test", None);
mem::drop(dpkg);
let dpkg = Dpkg::new("test2", None);
mem::drop(dpkg);
}
#[test]
fn test_new2() {
let dpkg = Dpkg::new("test", None);
mem::drop(dpkg);
}
In the first test, no matter how many times I invoke Dpkg::new()
everything is in order and works fine.
However, if I execute Dpkg::new()
in test_new2()
the result is either a segfault or the following error:
test: unrecoverable fatal error, aborting:
parsing file '/var/lib/dpkg/status' near line 10 package 'fonts-sil-abyssinica':
'Replaces' field, invalid package name '�': must start with an alphanumeric character
I'm not really sure why though. The following C program runs as expected:
#include <stdio.h>
#define LIBDPKG_VOLATILE_API
#include <dpkg/dpkg-db.h>
#include <dpkg/dpkg.h>
#include <stdlib.h>
void setup_dpkg(const char *program_name, const char *root_directory) {
dpkg_set_progname(program_name);
push_error_context();
if (root_directory != NULL) {
dpkg_db_set_dir(root_directory);
}
modstatdb_init();
modstatdb_open(msdbrw_available_readonly);
}
void destroy_dpkg() {
pkg_db_reset();
modstatdb_done();
pop_error_context(ehflag_normaltidy);
}
int main() {
for (int i =0; i < 100; i++) {
char *program_name = (char*)malloc(32 * sizeof(char));
strcpy(program_name, "test");
char *dir = (char*)malloc(16 * sizeof(char));
strcpy(dir, "/var/lib/dpkg");
setup_dpkg(program_name, dir);
printf("%d time!\n", i);
destroy_dpkg();
free(program_name);
free(dir);
}
return 0;
}
The output is:
0 time!
1 time!
2 time!
3 time!
4 time!
5 time!
6 time!
7 time!
8 time!
9 time!
10 time!
11 time!
12 time!
13 time!
14 time!
15 time!
16 time!
17 time!
18 time!
19 time!
20 time!
21 time!
22 time!
23 time!
24 time!
25 time!
26 time!
27 time!
28 time!
29 time!
30 time!
31 time!
32 time!
33 time!
34 time!
35 time!
36 time!
37 time!
38 time!
39 time!
40 time!
41 time!
42 time!
43 time!
44 time!
45 time!
46 time!
47 time!
48 time!
49 time!
50 time!
51 time!
52 time!
53 time!
54 time!
55 time!
56 time!
57 time!
58 time!
59 time!
60 time!
61 time!
62 time!
63 time!
64 time!
65 time!
66 time!
67 time!
68 time!
69 time!
70 time!
71 time!
72 time!
73 time!
74 time!
75 time!
76 time!
77 time!
78 time!
79 time!
80 time!
81 time!
82 time!
83 time!
84 time!
85 time!
86 time!
87 time!
88 time!
89 time!
90 time!
91 time!
92 time!
93 time!
94 time!
95 time!
96 time!
97 time!
98 time!
99 time!
Am I doing something wrong here (which is probable) or is it the Rust test runner which causes such problems?
The problem was not with my code but from the fact that libdpkg is not threadsafe.
This is fine from an implementation perspective but Rust runs the tests in parallel which causes the segfault.