Skip to content

Commit 009df08

Browse files
authored
Merge pull request #156 from gtsiam/panic-safety
Improved panic safety
2 parents e508910 + 0f44a64 commit 009df08

File tree

3 files changed

+135
-145
lines changed

3 files changed

+135
-145
lines changed

build.rs

Lines changed: 65 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -49,72 +49,53 @@ fn compile_linux() {
4949
// First check the features enabled for the crate.
5050
// Only one linux backend should be enabled at a time.
5151

52-
let avail_backends: [(&'static str, Box<dyn Fn()>); 6] = [
53-
(
54-
"LINUX_STATIC_HIDRAW",
55-
Box::new(|| {
56-
let mut config = cc::Build::new();
57-
println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c");
58-
config
59-
.file("etc/hidapi/linux/hid.c")
60-
.include("etc/hidapi/hidapi");
61-
pkg_config::probe_library("libudev").expect("Unable to find libudev");
62-
config.compile("libhidapi.a");
63-
println!("cargo:rustc-cfg=hidapi");
64-
}),
65-
),
66-
(
67-
"LINUX_STATIC_LIBUSB",
68-
Box::new(|| {
69-
let mut config = cc::Build::new();
70-
println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c");
71-
config
72-
.file("etc/hidapi/libusb/hid.c")
73-
.include("etc/hidapi/hidapi");
74-
let lib =
75-
pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0");
76-
for path in lib.include_paths {
77-
config.include(
78-
path.to_str()
79-
.expect("Failed to convert include path to str"),
80-
);
81-
}
82-
config.compile("libhidapi.a");
83-
println!("cargo:rustc-cfg=libusb");
84-
println!("cargo:rustc-cfg=hidapi");
85-
}),
86-
),
87-
(
88-
"LINUX_SHARED_HIDRAW",
89-
Box::new(|| {
90-
pkg_config::probe_library("hidapi-hidraw").expect("Unable to find hidapi-hidraw");
91-
println!("cargo:rustc-cfg=hidapi");
92-
}),
93-
),
94-
(
95-
"LINUX_SHARED_LIBUSB",
96-
Box::new(|| {
97-
pkg_config::probe_library("libusb-1.0").expect("Unable to find libusb-1.0");
98-
pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb");
99-
println!("cargo:rustc-cfg=libusb");
100-
println!("cargo:rustc-cfg=hidapi");
101-
}),
102-
),
103-
(
104-
"LINUX_NATIVE",
105-
Box::new(|| {
106-
// The udev crate takes care of finding its library
107-
}),
108-
),
109-
(
110-
"LINUX_NATIVE_BASIC_UDEV",
111-
Box::new(|| {
112-
// Enable `feature="linux-native"` to reuse the existing
113-
// linux-native code. It is considered an error in
114-
// basic-udev if this fails to compile.
115-
println!("cargo:rustc-cfg=feature=\"linux-native\"");
116-
}),
117-
),
52+
let avail_backends: [(&'static str, &dyn Fn()); 6] = [
53+
("LINUX_STATIC_HIDRAW", &|| {
54+
let mut config = cc::Build::new();
55+
println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c");
56+
config
57+
.file("etc/hidapi/linux/hid.c")
58+
.include("etc/hidapi/hidapi");
59+
pkg_config::probe_library("libudev").expect("Unable to find libudev");
60+
config.compile("libhidapi.a");
61+
println!("cargo:rustc-cfg=hidapi");
62+
}),
63+
("LINUX_STATIC_LIBUSB", &|| {
64+
let mut config = cc::Build::new();
65+
println!("cargo:rerun-if-changed=etc/hidapi/linux/hid.c");
66+
config
67+
.file("etc/hidapi/libusb/hid.c")
68+
.include("etc/hidapi/hidapi");
69+
let lib = pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0");
70+
for path in lib.include_paths {
71+
config.include(
72+
path.to_str()
73+
.expect("Failed to convert include path to str"),
74+
);
75+
}
76+
config.compile("libhidapi.a");
77+
println!("cargo:rustc-cfg=libusb");
78+
println!("cargo:rustc-cfg=hidapi");
79+
}),
80+
("LINUX_SHARED_HIDRAW", &|| {
81+
pkg_config::probe_library("hidapi-hidraw").expect("Unable to find hidapi-hidraw");
82+
println!("cargo:rustc-cfg=hidapi");
83+
}),
84+
("LINUX_SHARED_LIBUSB", &|| {
85+
pkg_config::probe_library("libusb-1.0").expect("Unable to find libusb-1.0");
86+
pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb");
87+
println!("cargo:rustc-cfg=libusb");
88+
println!("cargo:rustc-cfg=hidapi");
89+
}),
90+
("LINUX_NATIVE", &|| {
91+
// The udev crate takes care of finding its library
92+
}),
93+
("LINUX_NATIVE_BASIC_UDEV", &|| {
94+
// Enable `feature="linux-native"` to reuse the existing
95+
// linux-native code. It is considered an error in
96+
// basic-udev if this fails to compile.
97+
println!("cargo:rustc-cfg=feature=\"linux-native\"");
98+
}),
11899
];
119100

120101
let mut backends = avail_backends
@@ -155,31 +136,24 @@ fn compile_illumos() {
155136
// First check the features enabled for the crate.
156137
// Only one illumos backend should be enabled at a time.
157138

158-
let avail_backends: [(&'static str, Box<dyn Fn()>); 2] = [
159-
(
160-
"ILLUMOS_STATIC_LIBUSB",
161-
Box::new(|| {
162-
let mut config = cc::Build::new();
163-
config
164-
.file("etc/hidapi/libusb/hid.c")
165-
.include("etc/hidapi/hidapi");
166-
let lib =
167-
pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0");
168-
for path in lib.include_paths {
169-
config.include(
170-
path.to_str()
171-
.expect("Failed to convert include path to str"),
172-
);
173-
}
174-
config.compile("libhidapi.a");
175-
}),
176-
),
177-
(
178-
"ILLUMOS_SHARED_LIBUSB",
179-
Box::new(|| {
180-
pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb");
181-
}),
182-
),
139+
let avail_backends: [(&'static str, &dyn Fn()); 2] = [
140+
("ILLUMOS_STATIC_LIBUSB", &|| {
141+
let mut config = cc::Build::new();
142+
config
143+
.file("etc/hidapi/libusb/hid.c")
144+
.include("etc/hidapi/hidapi");
145+
let lib = pkg_config::find_library("libusb-1.0").expect("Unable to find libusb-1.0");
146+
for path in lib.include_paths {
147+
config.include(
148+
path.to_str()
149+
.expect("Failed to convert include path to str"),
150+
);
151+
}
152+
config.compile("libhidapi.a");
153+
}),
154+
("ILLUMOS_SHARED_LIBUSB", &|| {
155+
pkg_config::probe_library("hidapi-libusb").expect("Unable to find hidapi-libusb");
156+
}),
183157
];
184158

185159
let mut backends = avail_backends

examples/co2mon.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ fn decode_buf(buf: Packet) -> CO2Result {
7878
CO2Result::Concentration(val)
7979
}
8080
}
81-
_ => CO2Result::Unknown(res[0], val),
81+
_ => CO2Result::Unknown(kind, val),
8282
}
8383
}
8484

@@ -113,7 +113,7 @@ fn main() -> Result<(), HidError> {
113113
match decode_buf(buf) {
114114
CO2Result::Temperature(val) => println!("Temp:\t{val}"),
115115
CO2Result::Concentration(val) => println!("Conc:\t{val}"),
116-
CO2Result::Unknown(..) => (),
116+
CO2Result::Unknown(kind, val) => eprintln!("Unknown({kind}):\t{val}"),
117117
CO2Result::Error(msg) => {
118118
return Err(invalid_data_err(msg));
119119
}

src/lib.rs

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,44 @@ cfg_if! {
126126
pub type HidResult<T> = Result<T, HidError>;
127127
pub const MAX_REPORT_DESCRIPTOR_SIZE: usize = 4096;
128128

129-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129+
struct ContextState {
130+
device_discovery: bool,
131+
init_state: InitState,
132+
}
133+
130134
enum InitState {
131135
NotInit,
132-
Init { enumerate: bool },
136+
Init,
133137
}
134138

135-
static INIT_STATE: Mutex<InitState> = Mutex::new(InitState::NotInit);
139+
/// Global state to coordinate backing C library global context management.
140+
static CONTEXT_STATE: Mutex<ContextState> = Mutex::new(ContextState {
141+
device_discovery: true,
142+
init_state: InitState::NotInit,
143+
});
144+
145+
/// `hidapi` context.
146+
///
147+
/// The `hidapi` C library is lazily initialized when creating the first instance,
148+
/// and never deinitialized. Therefore, it is allowed to create multiple `HidApi`
149+
/// instances.
150+
///
151+
/// Each instance has its own device list cache.
152+
pub struct HidApi {
153+
device_list: Vec<DeviceInfo>,
154+
}
136155

137-
fn lazy_init(do_enumerate: bool) -> HidResult<()> {
138-
let mut init_state = INIT_STATE.lock().unwrap();
156+
impl HidApi {
157+
/// Create a new hidapi context.
158+
///
159+
/// Will also initialize the currently available device list if device discovery has not already
160+
/// been [disabled](Self::disable_device_discovery).
161+
pub fn new() -> HidResult<Self> {
162+
let mut state = CONTEXT_STATE.lock().unwrap();
139163

140-
match *init_state {
141-
InitState::NotInit => {
164+
if let InitState::NotInit = state.init_state {
142165
#[cfg(all(libusb, not(target_os = "freebsd")))]
143-
if !do_enumerate {
166+
if !state.device_discovery {
144167
// Do not scan for devices in libusb_init()
145168
// Must be set before calling it.
146169
// This is needed on Android, where access to USB devices is limited
@@ -158,42 +181,8 @@ fn lazy_init(do_enumerate: bool) -> HidResult<()> {
158181
ffi::macos::hid_darwin_set_open_exclusive(0)
159182
}
160183

161-
*init_state = InitState::Init {
162-
enumerate: do_enumerate,
163-
}
184+
state.init_state = InitState::Init;
164185
}
165-
InitState::Init { enumerate } => {
166-
if enumerate != do_enumerate {
167-
panic!("Trying to initialize hidapi with enumeration={}, but it is already initialized with enumeration={}.", do_enumerate, enumerate)
168-
}
169-
}
170-
}
171-
172-
Ok(())
173-
}
174-
175-
/// `hidapi` context.
176-
///
177-
/// The `hidapi` C library is lazily initialized when creating the first instance,
178-
/// and never deinitialized. Therefore, it is allowed to create multiple `HidApi`
179-
/// instances.
180-
///
181-
/// Each instance has its own device list cache.
182-
pub struct HidApi {
183-
device_list: Vec<DeviceInfo>,
184-
}
185-
186-
impl HidApi {
187-
/// Create a new hidapi context.
188-
///
189-
/// Will also initialize the currently available device list.
190-
///
191-
/// # Panics
192-
///
193-
/// Panics if hidapi is already initialized in "without enumerate" mode
194-
/// (i.e. if `new_without_enumerate()` has been called before).
195-
pub fn new() -> HidResult<Self> {
196-
lazy_init(true)?;
197186

198187
let mut api = HidApi {
199188
device_list: Vec::with_capacity(8),
@@ -202,20 +191,47 @@ impl HidApi {
202191
Ok(api)
203192
}
204193

205-
/// Create a new hidapi context, in "do not enumerate" mode.
194+
/// Disable device discovery on context creation.
206195
///
207-
/// This is needed on Android, where access to USB device enumeration is limited.
196+
/// This may be necessary on Android, where access to USB device enumeration is limited.
208197
///
209198
/// # Panics
210199
///
211-
/// Panics if hidapi is already initialized in "do enumerate" mode
212-
/// (i.e. if `new()` has been called before).
213-
pub fn new_without_enumerate() -> HidResult<Self> {
214-
lazy_init(false)?;
200+
/// Panics if an hidapi context has already been initialized with device discovery.
201+
///
202+
/// <section class="warning">
203+
///
204+
/// Avoid using this in library code, as it is an inherently global operation.
205+
///
206+
/// This function is intended to be called by code that knows the environment it is running in.
207+
/// Usually this means application code either directly, or through another abstraction.
208+
///
209+
/// </section>
210+
pub fn disable_device_discovery() {
211+
let mut state = CONTEXT_STATE.lock().unwrap();
212+
213+
if let InitState::NotInit = state.init_state {
214+
state.device_discovery = false; // Only disable device discovery before init.
215+
} else if state.device_discovery {
216+
core::mem::drop(state); // Make sure we don't poison the lock when panicking.
217+
panic!("Cannot disable device discovery after HidApi has been initialized");
218+
}
219+
}
215220

216-
Ok(HidApi {
217-
device_list: Vec::new(),
218-
})
221+
/// Create a new hidapi context, after disabling discovery. Please avoid using this function in
222+
/// library code, because it forces all instances of HidApi to disable device discovery.
223+
///
224+
/// See [`HidApi::disable_device_discovery()`].
225+
///
226+
/// # Panics
227+
///
228+
/// Panics if an hidapi context has already been initialized with device discovery.
229+
#[deprecated(
230+
note = "Please use only `HidApi::new()` in library code. Application code should disable device discovery explicitly."
231+
)]
232+
pub fn new_without_enumerate() -> HidResult<Self> {
233+
Self::disable_device_discovery();
234+
Self::new()
219235
}
220236

221237
/// Refresh devices list and information about them (to access them use

0 commit comments

Comments
 (0)