xref: /aosp_15_r20/external/flashrom/bindings/rust/libflashrom/src/lib.rs (revision 0d6140be3aa665ecc836e8907834fcd3e3b018fc)
1 /*
2  * This file is part of the flashrom project.
3  *
4  * Copyright (C) 2022 The Chromium OS Authors
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 
17 //! # libflashrom
18 //!
19 //! The `libflashrom` library is a rust FFI binding to the flashrom library.
20 //! libflashrom can be used to read write and modify some settings of flash chips.
21 //! The library closely follows the libflashrom C API, but exports a `safe` interface
22 //! including automatic resource management and forced error checking.
23 //!
24 //! libflashrom does not support threading, all usage of this library must occur on one thread.
25 //!
26 //! Most of the library functionality is defined on the [`Chip`] type.
27 //!
28 //! Example:
29 //!
30 //! ```
31 //! use libflashrom::*;
32 //! let mut chip = Chip::new(
33 //!     Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(),
34 //!     Some("W25Q128.V")
35 //! ).unwrap();
36 //! let mut buf = chip.image_read(None).unwrap();
37 //! buf[0] = 0xFE;
38 //! chip.image_write(&mut buf, None).unwrap();
39 //! ```
40 
41 use once_cell::sync::Lazy;
42 use regex::Regex;
43 use std::error;
44 use std::ffi::c_void;
45 use std::ffi::CStr;
46 use std::ffi::CString;
47 use std::fmt;
48 use std::io::Write;
49 use std::ptr::null;
50 use std::ptr::null_mut;
51 use std::ptr::NonNull;
52 use std::sync::Mutex;
53 use std::sync::Once;
54 
55 pub use libflashrom_sys::{
56     flashrom_log_level, FLASHROM_MSG_DEBUG, FLASHROM_MSG_DEBUG2, FLASHROM_MSG_ERROR,
57     FLASHROM_MSG_INFO, FLASHROM_MSG_SPEW, FLASHROM_MSG_WARN,
58 };
59 
60 pub use libflashrom_sys::flashrom_wp_mode;
61 
62 // libflashrom uses (start, len) or inclusive [start, end] for ranges.
63 // This type exists to rust RangeBounds types to a convenient internal format.
64 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
65 struct RangeInternal {
66     start: usize,
67     len: usize,
68 }
69 
70 /// The type returned for write protect and layout queries
71 pub type Range = std::ops::Range<usize>;
72 
73 impl<T> From<T> for RangeInternal
74 where
75     T: std::ops::RangeBounds<usize>,
76 {
from(range: T) -> Self77     fn from(range: T) -> Self {
78         let start = match range.start_bound() {
79             std::ops::Bound::Included(start) => *start,
80             std::ops::Bound::Excluded(start) => *start + 1,
81             std::ops::Bound::Unbounded => 0,
82         };
83         RangeInternal {
84             start,
85             len: match range.end_bound() {
86                 std::ops::Bound::Included(end) => *end - start + 1,
87                 std::ops::Bound::Excluded(end) => *end - start,
88                 std::ops::Bound::Unbounded => usize::MAX - start,
89             },
90         }
91     }
92 }
93 
94 impl RangeInternal {
95     // inclusive end for libflashrom
end(&self) -> usize96     fn end(&self) -> usize {
97         self.start + self.len - 1
98     }
99 }
100 
101 // log_c is set to be the callback at [`Programmer`] init. It deals with va_list and calls log_rust.
102 // log_rust calls a user defined function, or by default log_eprint.
103 // log_eprint just writes to stderr.
104 extern "C" {
set_log_callback()105     fn set_log_callback();
106     // Modifying and reading current_level is not thread safe, but neither is
107     // the libflashrom implementation, so we shouldnt be using threads anyway.
108     static mut current_level: libflashrom_sys::flashrom_log_level;
109 }
110 
111 /// Callers can use this function to log to the [`Logger`] they have set via [`set_log_level`]
112 ///
113 /// However from rust it is likely easier to call the [`Logger`] directly.
114 #[no_mangle]
log_rust( level: libflashrom_sys::flashrom_log_level, format: &std::os::raw::c_char, )115 pub extern "C" fn log_rust(
116     level: libflashrom_sys::flashrom_log_level,
117     format: &std::os::raw::c_char,
118 ) {
119     // Because this function is called from C, it must not panic.
120     // SAFETY: log_c always provides a non null ptr to a null terminated string
121     // msg does not outlive format.
122     let msg = unsafe { CStr::from_ptr(format) }.to_string_lossy();
123     // Locking can fail if a thread panics while holding the lock.
124     match LOG_FN.lock() {
125         Ok(g) => (*g)(level, msg.as_ref()),
126         Err(_) => eprintln!("ERROR: libflashrom log failure to lock function"),
127     };
128 }
129 
log_eprint(_level: libflashrom_sys::flashrom_log_level, msg: &str)130 fn log_eprint(_level: libflashrom_sys::flashrom_log_level, msg: &str) {
131     // Because this function is called from C, it must not panic.
132     // Ignore the error.
133     let _ = std::io::stderr().write_all(msg.as_bytes());
134 }
135 
136 // Can't directly atexit(flashrom_shutdown) because it is unsafe
shutdown_wrapper()137 extern "C" fn shutdown_wrapper() {
138     unsafe {
139         libflashrom_sys::flashrom_shutdown();
140     }
141 }
142 
143 /// A callback to log flashrom messages. This must not panic.
144 pub type Logger = fn(libflashrom_sys::flashrom_log_level, &str);
145 
146 static LOG_FN: Lazy<Mutex<Logger>> = Lazy::new(|| Mutex::new(log_eprint));
147 
148 /// Set the maximum log message level that will be passed to [`Logger`]
149 ///
150 /// log_rust and therefore the provided [`Logger`] will only be called for messages
151 /// greater or equal to the provided priority.
152 ///
153 /// ```
154 /// use libflashrom::set_log_level;
155 /// use libflashrom::FLASHROM_MSG_SPEW;
156 /// // Disable logging.
157 /// set_log_level(None);
158 /// // Log all messages  at priority FLASHROM_MSG_SPEW and above.
159 /// set_log_level(Some(FLASHROM_MSG_SPEW));
160 /// ```
set_log_level(level: Option<libflashrom_sys::flashrom_log_level>)161 pub fn set_log_level(level: Option<libflashrom_sys::flashrom_log_level>) {
162     // SAFETY: current_level is only read by log_c, in this thread.
163     match level {
164         Some(level) => unsafe { current_level = level + 1 },
165         None => unsafe { current_level = 0 },
166     };
167 }
168 
169 /// Set a [`Logger`] logging callback function
170 ///
171 /// Provided function must not panic, as it is called from C.
set_log_function(logger: Logger)172 pub fn set_log_function(logger: Logger) {
173     *LOG_FN.lock().unwrap() = logger;
174 }
175 
176 /// A type holding the error code returned by libflashrom and the function that returned the error.
177 ///
178 /// The error codes returned from each function differ in meaning, see libflashrom.h
179 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
180 pub struct ErrorCode {
181     function: &'static str,
182     code: i32,
183 }
184 
185 impl fmt::Display for ErrorCode {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result186     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187         write!(f, "libflashrom: {} returned {}", self.function, self.code)
188     }
189 }
190 
191 impl error::Error for ErrorCode {}
192 
193 impl From<ErrorCode> for String {
from(e: ErrorCode) -> Self194     fn from(e: ErrorCode) -> Self {
195         format!("{}", e)
196     }
197 }
198 
199 /// Errors from initialising libflashrom or a [`Programmer`]
200 #[derive(Clone, Debug, Eq, PartialEq)]
201 pub enum InitError {
202     DuplicateInit,
203     FlashromInit(ErrorCode),
204     InvalidName(std::ffi::NulError),
205     ProgrammerInit(ErrorCode),
206 }
207 
208 impl fmt::Display for InitError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result209     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210         write!(f, "{:?}", self)
211     }
212 }
213 
214 impl error::Error for InitError {}
215 
216 impl From<InitError> for String {
from(e: InitError) -> Self217     fn from(e: InitError) -> Self {
218         format!("{:?}", e)
219     }
220 }
221 
222 impl From<std::ffi::NulError> for InitError {
from(err: std::ffi::NulError) -> InitError223     fn from(err: std::ffi::NulError) -> InitError {
224         InitError::InvalidName(err)
225     }
226 }
227 
228 /// Errors from probing a [`Chip`]
229 #[derive(Clone, Debug, Eq, PartialEq)]
230 pub enum ChipInitError {
231     InvalidName(std::ffi::NulError),
232     NoChipError,
233     MultipleChipsError,
234     ProbeError,
235 }
236 
237 impl fmt::Display for ChipInitError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result238     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239         write!(f, "{:?}", self)
240     }
241 }
242 
243 impl error::Error for ChipInitError {}
244 
245 impl From<ChipInitError> for String {
from(e: ChipInitError) -> Self246     fn from(e: ChipInitError) -> Self {
247         format!("{:?}", e)
248     }
249 }
250 
251 impl From<std::ffi::NulError> for ChipInitError {
from(err: std::ffi::NulError) -> ChipInitError252     fn from(err: std::ffi::NulError) -> ChipInitError {
253         ChipInitError::InvalidName(err)
254     }
255 }
256 
257 #[derive(Clone, Debug, Eq, PartialEq)]
258 pub enum RegionError {
259     ErrorCode(ErrorCode),
260     InvalidName(std::ffi::NulError),
261 }
262 
263 impl fmt::Display for RegionError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result264     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265         write!(f, "{:?}", self)
266     }
267 }
268 
269 impl error::Error for RegionError {}
270 
271 impl From<RegionError> for String {
from(e: RegionError) -> Self272     fn from(e: RegionError) -> Self {
273         format!("{:?}", e)
274     }
275 }
276 
277 impl From<std::ffi::NulError> for RegionError {
from(err: std::ffi::NulError) -> RegionError278     fn from(err: std::ffi::NulError) -> RegionError {
279         RegionError::InvalidName(err)
280     }
281 }
282 
283 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
284 pub struct ParseLayoutError(String);
285 
286 impl fmt::Display for ParseLayoutError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result287     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288         write!(f, "{:?}", self)
289     }
290 }
291 
292 impl error::Error for ParseLayoutError {}
293 
294 impl From<ParseLayoutError> for String {
from(e: ParseLayoutError) -> Self295     fn from(e: ParseLayoutError) -> Self {
296         format!("{:?}", e)
297     }
298 }
299 
300 /// A translation of the flashrom_wp_result type
301 ///
302 /// WpOK is omitted, as it is not an error.
303 /// WpErrUnknown is used for an unknown error type.
304 /// Keep this list in sync with libflashrom.h
305 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
306 pub enum WPError {
307     WpErrChipUnsupported,
308     WpErrOther,
309     WpErrReadFailed,
310     WpErrWriteFailed,
311     WpErrVerifyFailed,
312     WpErrRangeUnsupported,
313     WpErrModeUnsupported,
314     WpErrRangeListUnavailable,
315     WpErrUnsupportedState,
316     WpErrUnknown(libflashrom_sys::flashrom_wp_result),
317 }
318 
319 impl From<libflashrom_sys::flashrom_wp_result> for WPError {
from(e: libflashrom_sys::flashrom_wp_result) -> Self320     fn from(e: libflashrom_sys::flashrom_wp_result) -> Self {
321         assert!(e != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK);
322         match e {
323             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_CHIP_UNSUPPORTED => {
324                 WPError::WpErrChipUnsupported
325             }
326             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_OTHER => WPError::WpErrOther,
327             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_READ_FAILED => {
328                 WPError::WpErrReadFailed
329             }
330             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_WRITE_FAILED => {
331                 WPError::WpErrWriteFailed
332             }
333             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_VERIFY_FAILED => {
334                 WPError::WpErrVerifyFailed
335             }
336             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_UNSUPPORTED => {
337                 WPError::WpErrRangeUnsupported
338             }
339             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_MODE_UNSUPPORTED => {
340                 WPError::WpErrModeUnsupported
341             }
342             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE => {
343                 WPError::WpErrRangeListUnavailable
344             }
345             libflashrom_sys::flashrom_wp_result::FLASHROM_WP_ERR_UNSUPPORTED_STATE => {
346                 WPError::WpErrUnsupportedState
347             }
348             _ => WPError::WpErrUnknown(e), // this could also be a panic
349         }
350     }
351 }
352 
353 impl fmt::Display for WPError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result354     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
355         write!(f, "{:?}", self)
356     }
357 }
358 
359 impl error::Error for WPError {}
360 
361 impl From<WPError> for String {
from(e: WPError) -> Self362     fn from(e: WPError) -> Self {
363         format!("{:?}", e)
364     }
365 }
366 
367 /// Return a rust sanitised string derived from flashrom_version_info.
flashrom_version_info() -> Option<String>368 pub fn flashrom_version_info() -> Option<String> {
369     let p = unsafe { libflashrom_sys::flashrom_version_info() };
370     if p.is_null() {
371         None
372     } else {
373         // SAFETY: flashrom_version_info returns a global `const char flashrom_version[]`
374         // derived from `-DFLASHROM_VERSION`, this is not guaranteed to be
375         // null terminated, but is in a normal build.
376         Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
377     }
378 }
379 
380 /// Structure for an initialised flashrom_programmer
381 // flashrom_programmer_init returns a pointer accepted by flashrom_flash_probe
382 // but this is not implemented at time of writing. When implemented the pointer
383 // can be stored here.
384 #[derive(Debug)]
385 pub struct Programmer {}
386 
387 /// Structure for an initialised flashrom chip, or flashrom_flashctx
388 // As returned by flashrom_flash_probe
389 // The layout is owned here as the chip only stores a pointer when a layout is set.
390 #[derive(Debug)]
391 pub struct Chip {
392     ctx: NonNull<libflashrom_sys::flashrom_flashctx>,
393     _programmer: Programmer,
394     layout: Option<Layout>,
395 }
396 
397 impl Programmer {
398     /// Initialise libflashrom and a programmer
399     ///
400     /// See libflashrom.h flashrom_programmer_init for argument documentation.
401     ///
402     /// Panics:
403     ///
404     /// If this libflashrom implementation returns a programmer pointer.
new( programmer_name: &str, programmer_options: Option<&str>, ) -> Result<Programmer, InitError>405     pub fn new(
406         programmer_name: &str,
407         programmer_options: Option<&str>,
408     ) -> Result<Programmer, InitError> {
409         static ONCE: Once = Once::new();
410         if ONCE.is_completed() {
411             // Flashrom does not currently support concurrent programmers
412             // Flashrom also does not currently support initialising a second programmer after a first has been initialised.
413             // This is used to warn the user if they try to initialise a second programmer.
414             return Err(InitError::DuplicateInit);
415         }
416         ONCE.call_once(|| {});
417 
418         static INIT_RES: Lazy<Result<(), InitError>> = Lazy::new(|| {
419             unsafe { set_log_callback() };
420             // always perform_selfcheck
421             let res = unsafe { libflashrom_sys::flashrom_init(1) };
422             if res == 0 {
423                 let res = unsafe { libc::atexit(shutdown_wrapper) };
424                 if res == 0 {
425                     Ok(())
426                 } else {
427                     unsafe { libflashrom_sys::flashrom_shutdown() };
428                     Err(InitError::FlashromInit(ErrorCode {
429                         function: "atexit",
430                         code: res,
431                     }))
432                 }
433             } else {
434                 Err(InitError::FlashromInit(ErrorCode {
435                     function: "flashrom_init",
436                     code: res,
437                 }))
438             }
439         });
440         (*INIT_RES).clone()?;
441 
442         let mut programmer: *mut libflashrom_sys::flashrom_programmer = null_mut();
443         let programmer_name = CString::new(programmer_name)?;
444         let programmer_options = match programmer_options {
445             Some(programmer_options) => Some(CString::new(programmer_options)?),
446             None => None,
447         };
448         let res = unsafe {
449             libflashrom_sys::flashrom_programmer_init(
450                 &mut programmer,
451                 programmer_name.as_ptr(),
452                 programmer_options.as_ref().map_or(null(), |x| x.as_ptr()),
453             )
454         };
455         if res != 0 {
456             Err(InitError::ProgrammerInit(ErrorCode {
457                 function: "flashrom_programmer_init",
458                 code: res,
459             }))
460         } else if !programmer.is_null() {
461             panic!("flashrom_programmer_init returning a programmer pointer is not supported")
462         } else {
463             Ok(Programmer {})
464         }
465     }
466 }
467 
468 impl Drop for Programmer {
drop(&mut self)469     fn drop(&mut self) {
470         unsafe {
471             libflashrom_sys::flashrom_programmer_shutdown(null_mut());
472         }
473     }
474 }
475 
476 /// A translation of the flashrom_flag type
477 ///
478 /// Keep this list in sync with libflashrom.h
479 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
480 pub enum FlashromFlag {
481     FlashromFlagForce,
482     FlashromFlagForceBoardmismatch,
483     FlashromFlagVerifyAfterWrite,
484     FlashromFlagVerifyWholeChip,
485     FlashromFlagSkipUnreadableRegions,
486     FlashromFlagSkipUnwritableRegions,
487 }
488 
489 impl From<FlashromFlag> for libflashrom_sys::flashrom_flag {
from(e: FlashromFlag) -> Self490     fn from(e: FlashromFlag) -> Self {
491         match e {
492             FlashromFlag::FlashromFlagForce => libflashrom_sys::flashrom_flag::FLASHROM_FLAG_FORCE,
493             FlashromFlag::FlashromFlagForceBoardmismatch => {
494                 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_FORCE_BOARDMISMATCH
495             }
496             FlashromFlag::FlashromFlagVerifyAfterWrite => {
497                 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_VERIFY_AFTER_WRITE
498             }
499             FlashromFlag::FlashromFlagVerifyWholeChip => {
500                 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_VERIFY_WHOLE_CHIP
501             }
502             FlashromFlag::FlashromFlagSkipUnreadableRegions => {
503                 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_SKIP_UNREADABLE_REGIONS
504             }
505             FlashromFlag::FlashromFlagSkipUnwritableRegions => {
506                 libflashrom_sys::flashrom_flag::FLASHROM_FLAG_SKIP_UNWRITABLE_REGIONS
507             }
508             e => panic!("Unexpected FlashromFlag: {:?}", e),
509         }
510     }
511 }
512 
513 /// Various flags of the flashrom context
514 ///
515 /// Keep the struct in sync with the flags in flash.h
516 #[derive(Debug)]
517 pub struct FlashromFlags {
518     pub force: bool,
519     pub force_boardmismatch: bool,
520     pub verify_after_write: bool,
521     pub verify_whole_chip: bool,
522     pub skip_unreadable_regions: bool,
523     pub skip_unwritable_regions: bool,
524 }
525 
526 /// Keep the default values in sync with cli_classic.c
527 impl Default for FlashromFlags {
default() -> Self528     fn default() -> Self {
529         Self {
530             force: false,
531             force_boardmismatch: false,
532             verify_after_write: true,
533             verify_whole_chip: true,
534             // These flags are introduced to address issues related to CSME locking parts. Setting
535             // them to true as default values
536             skip_unreadable_regions: true,
537             skip_unwritable_regions: true,
538         }
539     }
540 }
541 
542 impl fmt::Display for FlashromFlags {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result543     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544         write!(
545             f,
546             "FlashromFlags {{ force: {}, force_boardmismatch: {}, verify_after_write: {}, verify_whole_chip: {}, skip_unreadable_regions: {}, skip_unwritable_regions: {} }}",
547             self.force,
548             self.force_boardmismatch,
549             self.verify_after_write,
550             self.verify_whole_chip,
551             self.skip_unreadable_regions,
552             self.skip_unwritable_regions,
553         )
554     }
555 }
556 
557 impl Chip {
558     /// Probe for a chip
559     ///
560     /// See libflashrom.h flashrom_flash_probe for argument documentation.
new(programmer: Programmer, chip_name: Option<&str>) -> Result<Chip, ChipInitError>561     pub fn new(programmer: Programmer, chip_name: Option<&str>) -> Result<Chip, ChipInitError> {
562         let mut flash_ctx: *mut libflashrom_sys::flashrom_flashctx = null_mut();
563         let chip_name = match chip_name {
564             Some(chip_name) => Some(CString::new(chip_name)?),
565             None => None,
566         };
567         match unsafe {
568             libflashrom_sys::flashrom_flash_probe(
569                 &mut flash_ctx,
570                 null(),
571                 chip_name.as_ref().map_or(null(), |x| x.as_ptr()),
572             )
573         } {
574             0 => Ok(Chip {
575                 ctx: NonNull::new(flash_ctx).expect("flashrom_flash_probe returned null"),
576                 _programmer: programmer,
577                 layout: None,
578             }),
579             3 => Err(ChipInitError::MultipleChipsError),
580             2 => Err(ChipInitError::NoChipError),
581             _ => Err(ChipInitError::ProbeError),
582         }
583     }
584 
get_size(&self) -> usize585     pub fn get_size(&self) -> usize {
586         unsafe { libflashrom_sys::flashrom_flash_getsize(self.ctx.as_ref()) }
587     }
588 
589     /// Read the write protect config of this [`Chip`]
get_wp(&mut self) -> std::result::Result<WriteProtectCfg, WPError>590     pub fn get_wp(&mut self) -> std::result::Result<WriteProtectCfg, WPError> {
591         let mut cfg = WriteProtectCfg::new()?;
592         let res =
593             unsafe { libflashrom_sys::flashrom_wp_read_cfg(cfg.wp.as_mut(), self.ctx.as_mut()) };
594         if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
595             return Err(res.into());
596         }
597         Ok(cfg)
598     }
599 
600     /// Set the write protect config of this [`Chip`]
set_wp(&mut self, wp: &WriteProtectCfg) -> std::result::Result<(), WPError>601     pub fn set_wp(&mut self, wp: &WriteProtectCfg) -> std::result::Result<(), WPError> {
602         let res =
603             unsafe { libflashrom_sys::flashrom_wp_write_cfg(self.ctx.as_mut(), wp.wp.as_ref()) };
604         if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
605             return Err(res.into());
606         }
607         Ok(())
608     }
609 
610     /// Read the write protect ranges of this [`Chip`]
611     ///
612     /// # Panics
613     ///
614     /// Panics if flashrom_wp_get_available_ranges returns FLASHROM_WP_OK and a NULL pointer.
get_wp_ranges(&mut self) -> std::result::Result<Vec<Range>, WPError>615     pub fn get_wp_ranges(&mut self) -> std::result::Result<Vec<Range>, WPError> {
616         let mut ranges: *mut libflashrom_sys::flashrom_wp_ranges = null_mut();
617         let res = unsafe {
618             libflashrom_sys::flashrom_wp_get_available_ranges(&mut ranges, self.ctx.as_mut())
619         };
620         if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
621             return Err(res.into());
622         }
623         let ranges = WriteProtectRanges {
624             ranges: NonNull::new(ranges).expect("flashrom_wp_get_available_ranges returned null"),
625         };
626 
627         let count =
628             unsafe { libflashrom_sys::flashrom_wp_ranges_get_count(ranges.ranges.as_ref()) };
629         let mut ret = Vec::with_capacity(count);
630         for index in 0..count {
631             let mut start = 0;
632             let mut len = 0;
633             let res = unsafe {
634                 libflashrom_sys::flashrom_wp_ranges_get_range(
635                     &mut start,
636                     &mut len,
637                     ranges.ranges.as_ref(),
638                     // TODO: fix after https://review.coreboot.org/c/flashrom/+/64996
639                     index
640                         .try_into()
641                         .expect("flashrom_wp_ranges_get_count does not fit in a u32"),
642                 )
643             };
644             if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
645                 return Err(res.into());
646             }
647             ret.push(start..(start + len))
648         }
649         Ok(ret)
650     }
651 
652     /// Returns the layout read from the fmap of this [`Chip`]
653     ///
654     /// # Panics
655     ///
656     /// Panics if flashrom_layout_read_fmap_from_rom returns FLASHROM_WP_OK and a NULL pointer.
layout_read_fmap_from_rom(&mut self) -> std::result::Result<Layout, ErrorCode>657     pub fn layout_read_fmap_from_rom(&mut self) -> std::result::Result<Layout, ErrorCode> {
658         let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut();
659         let err = unsafe {
660             libflashrom_sys::flashrom_layout_read_fmap_from_rom(
661                 &mut layout,
662                 self.ctx.as_mut(),
663                 0,
664                 self.get_size(),
665             )
666         };
667         if err != 0 {
668             return Err(ErrorCode {
669                 function: "flashrom_layout_read_fmap_from_rom",
670                 code: err,
671             });
672         }
673         Ok(Layout {
674             layout: NonNull::new(layout).expect("flashrom_layout_read_fmap_from_rom returned null"),
675         })
676     }
677 
678     /// Sets the layout of this [`Chip`]
679     ///
680     /// [`Chip`] takes ownership of Layout to ensure it is not released before the [`Chip`].
set_layout(&mut self, layout: Layout)681     fn set_layout(&mut self, layout: Layout) {
682         unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), layout.layout.as_ref()) };
683         self.layout = Some(layout)
684     }
685 
unset_layout(&mut self) -> Option<Layout>686     fn unset_layout(&mut self) -> Option<Layout> {
687         unsafe { libflashrom_sys::flashrom_layout_set(self.ctx.as_mut(), null()) };
688         self.layout.take()
689     }
690 
691     /// Read the whole [`Chip`], or a portion specified in a Layout
image_read( &mut self, layout: Option<Layout>, ) -> std::result::Result<Vec<u8>, ErrorCode>692     pub fn image_read(
693         &mut self,
694         layout: Option<Layout>,
695     ) -> std::result::Result<Vec<u8>, ErrorCode> {
696         if let Some(layout) = layout {
697             self.set_layout(layout);
698         }
699         let len = self.get_size();
700         let mut buf = vec![0; len];
701         let res = unsafe {
702             libflashrom_sys::flashrom_image_read(
703                 self.ctx.as_mut(),
704                 buf.as_mut_ptr() as *mut c_void,
705                 len,
706             )
707         };
708         self.unset_layout();
709 
710         if res == 0 {
711             Ok(buf)
712         } else {
713             Err(ErrorCode {
714                 function: "flashrom_image_read",
715                 code: res,
716             })
717         }
718     }
719 
720     /// Write the whole [`Chip`], or a portion specified in a Layout
image_write( &mut self, buf: &mut [u8], layout: Option<Layout>, ) -> std::result::Result<(), ErrorCode>721     pub fn image_write(
722         &mut self,
723         buf: &mut [u8],
724         layout: Option<Layout>,
725     ) -> std::result::Result<(), ErrorCode> {
726         if let Some(layout) = layout {
727             self.set_layout(layout);
728         }
729         let res = unsafe {
730             libflashrom_sys::flashrom_image_write(
731                 self.ctx.as_mut(),
732                 buf.as_mut_ptr() as *mut c_void,
733                 buf.len(),
734                 null(),
735             )
736         };
737         self.unset_layout();
738 
739         if res == 0 {
740             Ok(())
741         } else {
742             Err(ErrorCode {
743                 function: "flashrom_image_write",
744                 code: res,
745             })
746         }
747     }
748 
749     /// Verify the whole [`Chip`], or a portion specified in a Layout
image_verify( &mut self, buf: &[u8], layout: Option<Layout>, ) -> std::result::Result<(), ErrorCode>750     pub fn image_verify(
751         &mut self,
752         buf: &[u8],
753         layout: Option<Layout>,
754     ) -> std::result::Result<(), ErrorCode> {
755         if let Some(layout) = layout {
756             self.set_layout(layout);
757         }
758         let res = unsafe {
759             libflashrom_sys::flashrom_image_verify(
760                 self.ctx.as_mut(),
761                 buf.as_ptr() as *const c_void,
762                 buf.len(),
763             )
764         };
765         self.unset_layout();
766 
767         if res == 0 {
768             Ok(())
769         } else {
770             Err(ErrorCode {
771                 function: "flashrom_image_verify",
772                 code: res,
773             })
774         }
775     }
776 
777     /// Erase the whole [`Chip`]
erase(&mut self) -> std::result::Result<(), ErrorCode>778     pub fn erase(&mut self) -> std::result::Result<(), ErrorCode> {
779         let res = unsafe { libflashrom_sys::flashrom_flash_erase(self.ctx.as_mut()) };
780         if res == 0 {
781             Ok(())
782         } else {
783             Err(ErrorCode {
784                 function: "flashrom_flash_erase",
785                 code: res,
786             })
787         }
788     }
789 
790     /// Set a flag in the given flash context
flag_set(&mut self, flag: FlashromFlag, value: bool) -> ()791     pub fn flag_set(&mut self, flag: FlashromFlag, value: bool) -> () {
792         unsafe { libflashrom_sys::flashrom_flag_set(self.ctx.as_mut(), flag.into(), value) }
793     }
794 }
795 
796 impl Drop for Chip {
drop(&mut self)797     fn drop(&mut self) {
798         unsafe {
799             libflashrom_sys::flashrom_flash_release(self.ctx.as_mut());
800         }
801     }
802 }
803 
804 /// Structure for an initialised flashrom_wp_cfg
805 #[derive(Debug)]
806 pub struct WriteProtectCfg {
807     wp: NonNull<libflashrom_sys::flashrom_wp_cfg>,
808 }
809 
810 impl WriteProtectCfg {
811     /// Create an empty [`WriteProtectCfg`]
812     ///
813     /// # Panics
814     ///
815     /// Panics if flashrom_wp_cfg_new returns FLASHROM_WP_OK and a NULL pointer.
new() -> std::result::Result<WriteProtectCfg, WPError>816     pub fn new() -> std::result::Result<WriteProtectCfg, WPError> {
817         let mut cfg: *mut libflashrom_sys::flashrom_wp_cfg = null_mut();
818         let res = unsafe { libflashrom_sys::flashrom_wp_cfg_new(&mut cfg) };
819         if res != libflashrom_sys::flashrom_wp_result::FLASHROM_WP_OK {
820             return Err(res.into());
821         }
822         Ok(WriteProtectCfg {
823             wp: NonNull::new(cfg).expect("flashrom_wp_cfg_new returned null"),
824         })
825     }
826 
get_mode(&self) -> libflashrom_sys::flashrom_wp_mode827     pub fn get_mode(&self) -> libflashrom_sys::flashrom_wp_mode {
828         unsafe { libflashrom_sys::flashrom_wp_get_mode(self.wp.as_ref()) }
829     }
830 
set_mode(&mut self, mode: libflashrom_sys::flashrom_wp_mode)831     pub fn set_mode(&mut self, mode: libflashrom_sys::flashrom_wp_mode) {
832         unsafe { libflashrom_sys::flashrom_wp_set_mode(self.wp.as_mut(), mode) }
833     }
834 
get_range(&self) -> Range835     pub fn get_range(&self) -> Range {
836         let mut start = 0;
837         let mut len = 0;
838         unsafe { libflashrom_sys::flashrom_wp_get_range(&mut start, &mut len, self.wp.as_ref()) };
839         start..(start + len)
840     }
841 
set_range<T>(&mut self, range: T) where T: std::ops::RangeBounds<usize>,842     pub fn set_range<T>(&mut self, range: T)
843     where
844         T: std::ops::RangeBounds<usize>,
845     {
846         let range: RangeInternal = range.into();
847         unsafe { libflashrom_sys::flashrom_wp_set_range(self.wp.as_mut(), range.start, range.len) }
848     }
849 }
850 
851 impl Drop for WriteProtectCfg {
drop(&mut self)852     fn drop(&mut self) {
853         unsafe {
854             libflashrom_sys::flashrom_wp_cfg_release(self.wp.as_mut());
855         }
856     }
857 }
858 
859 impl fmt::Display for WriteProtectCfg {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result860     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
861         write!(f, "range:{:?} mode:{:?}", self.get_range(), self.get_mode())
862     }
863 }
864 
865 #[derive(Debug)]
866 struct WriteProtectRanges {
867     ranges: NonNull<libflashrom_sys::flashrom_wp_ranges>,
868 }
869 
870 impl WriteProtectRanges {}
871 
872 impl Drop for WriteProtectRanges {
drop(&mut self)873     fn drop(&mut self) {
874         unsafe {
875             libflashrom_sys::flashrom_wp_ranges_release(self.ranges.as_mut());
876         }
877     }
878 }
879 
880 /// Structure for an initialised flashrom_layout
881 #[derive(Debug)]
882 pub struct Layout {
883     layout: NonNull<libflashrom_sys::flashrom_layout>,
884 }
885 
886 impl Layout {
887     /// Create an empty [`Layout`]
888     ///
889     /// # Panics
890     ///
891     /// Panics if flashrom_layout_new returns 0 and a NULL pointer.
new() -> std::result::Result<Layout, ErrorCode>892     pub fn new() -> std::result::Result<Layout, ErrorCode> {
893         let mut layout: *mut libflashrom_sys::flashrom_layout = null_mut();
894         let err = unsafe { libflashrom_sys::flashrom_layout_new(&mut layout) };
895         if err != 0 {
896             Err(ErrorCode {
897                 function: "flashrom_layout_new",
898                 code: err,
899             })
900         } else {
901             Ok(Layout {
902                 layout: NonNull::new(layout).expect("flashrom_layout_new returned null"),
903             })
904         }
905     }
906 
907     /// Add a region to the [`Layout`]
908     ///
909     /// Not the region will not be 'included', include_region must be called to include the region.
910     ///
911     /// # Errors
912     ///
913     /// This function will return an error if the region is not a valid CString,
914     /// or if libflashrom returns an error.
add_region<T>(&mut self, region: &str, range: T) -> std::result::Result<(), RegionError> where T: std::ops::RangeBounds<usize>,915     pub fn add_region<T>(&mut self, region: &str, range: T) -> std::result::Result<(), RegionError>
916     where
917         T: std::ops::RangeBounds<usize>,
918     {
919         let range: RangeInternal = range.into();
920         let err = {
921             let region = CString::new(region)?;
922             unsafe {
923                 libflashrom_sys::flashrom_layout_add_region(
924                     self.layout.as_mut(),
925                     range.start,
926                     range.end(),
927                     region.as_ptr(),
928                 )
929             }
930         };
931 
932         if err != 0 {
933             Err(RegionError::ErrorCode(ErrorCode {
934                 function: "flashrom_layout_add_region",
935                 code: err,
936             }))
937         } else {
938             Ok(())
939         }
940     }
941 
942     /// Include a region
943     ///
944     /// # Errors
945     ///
946     /// This function will return an error if the region is not a valid CString,
947     /// or if libflashrom returns an error.
include_region(&mut self, region: &str) -> std::result::Result<(), RegionError>948     pub fn include_region(&mut self, region: &str) -> std::result::Result<(), RegionError> {
949         let err = {
950             let region = CString::new(region)?;
951             unsafe {
952                 libflashrom_sys::flashrom_layout_include_region(
953                     self.layout.as_mut(),
954                     region.as_ptr(),
955                 )
956             }
957         };
958         if err != 0 {
959             Err(RegionError::ErrorCode(ErrorCode {
960                 function: "flashrom_layout_include_region",
961                 code: err,
962             }))
963         } else {
964             Ok(())
965         }
966     }
967 
968     /// Exclude a region
969     ///
970     /// # Errors
971     ///
972     /// This function will return an error if the region is not a valid CString,
973     /// or if libflashrom returns an error.
exclude_region(&mut self, region: &str) -> std::result::Result<(), RegionError>974     pub fn exclude_region(&mut self, region: &str) -> std::result::Result<(), RegionError> {
975         let err = {
976             let region = CString::new(region)?;
977             unsafe {
978                 libflashrom_sys::flashrom_layout_exclude_region(
979                     self.layout.as_mut(),
980                     region.as_ptr(),
981                 )
982             }
983         };
984         if err != 0 {
985             Err(RegionError::ErrorCode(ErrorCode {
986                 function: "flashrom_layout_exclude_region",
987                 code: err,
988             }))
989         } else {
990             Ok(())
991         }
992     }
993 
994     /// Get the [`Range`] for the given region
995     ///
996     /// # Errors
997     ///
998     /// This function will return an error if the region is not a valid CString,
999     /// or if libflashrom returns an error.
get_region_range(&mut self, region: &str) -> std::result::Result<Range, RegionError>1000     pub fn get_region_range(&mut self, region: &str) -> std::result::Result<Range, RegionError> {
1001         let mut start: std::os::raw::c_uint = 0;
1002         let mut len: std::os::raw::c_uint = 0;
1003         let err = {
1004             let region = CString::new(region)?;
1005             unsafe {
1006                 libflashrom_sys::flashrom_layout_get_region_range(
1007                     self.layout.as_mut(),
1008                     region.as_ptr(),
1009                     &mut start,
1010                     &mut len,
1011                 )
1012             }
1013         };
1014         if err != 0 {
1015             Err(RegionError::ErrorCode(ErrorCode {
1016                 function: "flashrom_layout_get_region_range",
1017                 code: err,
1018             }))
1019         } else {
1020             // should be safe to assume sizeof(size_t) >= sizeof(unsigned int)
1021             // TODO: fix after https://review.coreboot.org/c/flashrom/+/65944
1022             Ok(start.try_into().unwrap()..(start + len).try_into().unwrap())
1023         }
1024     }
1025 }
1026 
1027 // TODO this will be replaced with an API implementation: https://review.coreboot.org/c/flashrom/+/65999
1028 impl std::str::FromStr for Layout {
1029     type Err = Box<dyn error::Error>;
1030 
1031     /// This will attempt to parse the [`Layout`] file format into a [`Layout`]
1032     ///
1033     /// The format is documented in the flashrom man page.
from_str(s: &str) -> std::result::Result<Self, Self::Err>1034     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1035         let mut ret = Layout::new()?;
1036 
1037         // format is hex:hex name
1038         // flashrom layout.c seems to allow any characters in the name string
1039         // I am restricting here to non whitespace
1040         let re = Regex::new(r"^([0-9A-Za-z]+):([0-9A-Za-z]+)\s+(\S+)$").unwrap();
1041 
1042         // we dont use captures_iter else we would ignore incorrect lines
1043         for line in s.lines() {
1044             let (start, end, name) = match re.captures(line) {
1045                 Some(caps) => (
1046                     caps.get(1).unwrap(),
1047                     caps.get(2).unwrap(),
1048                     caps.get(3).unwrap(),
1049                 ),
1050                 None => Err(ParseLayoutError(format!("failed to parse: {:?}", line)))?,
1051             };
1052             let start = usize::from_str_radix(start.as_str(), 16)?;
1053             let end = usize::from_str_radix(end.as_str(), 16)?;
1054             ret.add_region(name.as_str(), start..=end)?;
1055         }
1056 
1057         Ok(ret)
1058     }
1059 }
1060 
1061 impl Drop for Layout {
drop(&mut self)1062     fn drop(&mut self) {
1063         unsafe {
1064             libflashrom_sys::flashrom_layout_release(self.layout.as_mut());
1065         }
1066     }
1067 }
1068 
1069 #[cfg(test)]
1070 mod tests {
1071     use gag::BufferRedirect;
1072     use std::io::Read;
1073 
1074     use super::flashrom_version_info;
1075     use super::set_log_level;
1076     use super::Chip;
1077     use super::ChipInitError;
1078     use super::InitError;
1079     use crate::set_log_function;
1080     use crate::Layout;
1081     use crate::Programmer;
1082     use crate::WriteProtectCfg;
1083 
1084     // flashrom contains global state, which prevents correct initialisation of
1085     // a second programmer or probing of a second chip. Run all unit tests in
1086     // forked subprocesses to avoid this issue.
1087     use rusty_fork::rusty_fork_test;
1088     rusty_fork_test! {
1089 
1090         #[test]
1091         fn version() {
1092             // There is no version requirement yet, but for example:
1093             // assert!(flashrom_version_info().contains("v1.2"))
1094             assert!(!flashrom_version_info().unwrap().is_empty())
1095         }
1096 
1097         #[test]
1098         fn only_one_programmer() {
1099             {
1100                 let _1 = Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap();
1101                 // Only one programmer can be initialised at a time.
1102                 assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit)
1103             }
1104             // Only one programmer can ever be initialised
1105             assert_eq!(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap_err(), InitError::DuplicateInit)
1106         }
1107 
1108         #[test]
1109         fn programmer_bad_cstring_name() {
1110             assert!(matches!(Programmer::new("dummy\0", None).unwrap_err(), InitError::InvalidName(_)))
1111         }
1112 
1113         #[test]
1114         fn chip_none() {
1115             // Not specifying a chip will select one if there is one.
1116             Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), None).unwrap();
1117         }
1118 
1119         #[test]
1120         fn chip_some() {
1121             // Specifying a valid chip.
1122             Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1123         }
1124 
1125         #[test]
1126         fn chip_nochip() {
1127             // Choosing a non existent chip fails.
1128             assert_eq!(
1129                 Chip::new(Programmer::new("dummy", None).unwrap(), Some("W25Q128.V")).unwrap_err(),
1130                 ChipInitError::NoChipError
1131             );
1132         }
1133 
1134         #[test]
1135         fn logging_stderr() {
1136             let mut buf = BufferRedirect::stderr().unwrap();
1137             let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1138 
1139             set_log_level(Some(libflashrom_sys::FLASHROM_MSG_INFO));
1140             fc.image_read(None).unwrap();
1141             let mut stderr = String::new();
1142             if buf.read_to_string(&mut stderr).unwrap() == 0 {
1143                 panic!("stderr empty when it should have some messages");
1144             }
1145 
1146             set_log_level(None);
1147             fc.image_read(None).unwrap();
1148             if buf.read_to_string(&mut stderr).unwrap() != 0 {
1149                 panic!("stderr not empty when it should be silent");
1150             }
1151         }
1152 
1153         #[test]
1154         fn logging_custom() {
1155             // Check that a custom logging callback works
1156             static mut BUF: String = String::new();
1157             fn logger(
1158                 _: libflashrom_sys::flashrom_log_level,
1159                 format: &str,
1160             ) {
1161                 unsafe {BUF.push_str(format)}
1162             }
1163             set_log_function(logger);
1164             set_log_level(Some(libflashrom_sys::FLASHROM_MSG_SPEW));
1165             Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1166             assert_ne!(unsafe{BUF.len()}, 0);
1167         }
1168 
1169         #[test]
1170         fn flashchip() {
1171             // basic tests of the flashchip methods
1172             let mut fc = Chip::new(Programmer::new("dummy", Some("emulate=W25Q128FV")).unwrap(), Some("W25Q128.V")).unwrap();
1173             fc.get_size();
1174 
1175             let mut wp = fc.get_wp().unwrap();
1176             wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1177             fc.set_wp(&wp).unwrap();
1178             fc.get_wp_ranges().unwrap();
1179 
1180             fn test_layout() -> Layout {
1181                 let mut layout = Layout::new().unwrap();
1182                 layout.add_region("xyz", 100..200).unwrap();
1183                 layout.include_region("xyz").unwrap();
1184                 layout.add_region("abc", 100..200).unwrap();
1185                 layout
1186             }
1187 
1188             fc.image_read(None).unwrap();
1189             fc.image_read(Some(test_layout())).unwrap();
1190 
1191             let mut buf = vec![0; fc.get_size()];
1192             fc.image_write(&mut buf, None).unwrap();
1193             fc.image_write(&mut buf, Some(test_layout())).unwrap();
1194 
1195             fc.image_verify(&buf, None).unwrap();
1196             fc.image_verify(&buf, Some(test_layout())).unwrap();
1197 
1198             fc.erase().unwrap();
1199         }
1200 
1201         #[test]
1202         fn write_protect() {
1203             let mut wp = WriteProtectCfg::new().unwrap();
1204             wp.set_mode(libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1205             wp.set_range(100..200);
1206             assert_eq!(wp.get_mode(), libflashrom_sys::flashrom_wp_mode::FLASHROM_WP_MODE_DISABLED);
1207             assert_eq!(wp.get_range(), 100..200);
1208         }
1209     }
1210 }
1211