semihosting_files/
lib.rs

1#![no_std]
2#![allow(clippy::result_unit_err)]
3#![warn(missing_docs)]
4
5//! This crate allows to access the files of a host machine while semihosting.
6//! It should work for all Cortex-M processors.
7//! 
8//! For examples on how to use this crate, check out the examples on [github](https://github.com/LuksEmbeddedCorner/semihosting-files/tree/master/examples).
9//!
10//! Click [here](https://developer.arm.com/documentation/dui0471/m/what-is-semihosting-/what-is-semihosting-?lang=en) for a reference to the underlying API.
11
12#[macro_use]
13extern crate cortex_m_semihosting;
14
15use core::mem;
16
17use cortex_m_semihosting::nr::open;
18
19use cstr_core::CStr;
20
21// FIXME Wait for a working core_io version
22// #[cfg(feature="core_io")]
23// mod core_io;
24
25/// A reference to an open file on the host system.
26///
27/// Depending on the options that it was opened with, files can be read and/or written.
28///
29/// Files are auromatically closed when they are dropped. If an error occurs while dropping, the error is silently swallowed.
30/// Use [`close`](File::close) if these errors should be explicitly handled.
31///
32/// Because all semihosting operations are very slow, buffering reads and writes might be beneficial.
33///
34/// As the semihosting operations don't return a specific error when they fail, most methods just return a `Result<_, ()>`.
35#[derive(Debug, Hash, PartialEq, Eq)]
36pub struct File {
37    handle: isize,
38}
39
40/// This enum determines how a file should be opened.
41/// 
42/// For more informations on the open modes, see the c-function `fopen`.
43#[repr(usize)]
44#[allow(missing_docs)]
45pub enum FileOpenMode {
46    Read = open::R,
47    ReadBinary = open::R_BINARY,
48    ReadWrite = open::RW,
49    ReadWriteAppend = open::RW_APPEND,
50    ReadWriteAppendBinary = open::RW_APPEND_BINARY,
51    ReadWriteTruncate = open::RW_TRUNC,
52    ReadWriteTruncateBinary = open::RW_TRUNC_BINARY,
53    WriteAppend = open::W_APPEND,
54    WriteAppendBinary = open::W_APPEND_BINARY,
55    WriteTruncate = open::W_TRUNC,
56    WriteTruncateBinary = open::W_TRUNC_BINARY,
57}
58
59/// Specifies the cursor position for [`File::seek`]
60pub enum SeekFrom {
61    /// Sets the cursor to the specified number of bytes, starting from the beginning of the file
62    ///
63    /// The provided number must be ranging from 0 to the length of the file
64    Start(usize),
65    /// Sets the cursor to the length of the file, plus the specified amount.
66    ///
67    /// Note that the cursor cannot be set to a absolute position greater than the length of the file,
68    /// so the relative position provided must be number ranging from the negative length of the file to 0.
69    End(isize),
70}
71
72/// An Error that occured during seeking
73#[derive(Debug, PartialEq, Eq, Hash)]
74pub enum SeekError {
75    /// The specified position was outside the boundary of the file
76    PositionOutOfBounds,
77    /// An unknown error occured while reading the length of the file, or setting the cursor.
78    Unknown,
79}
80
81impl File {
82    /// Opens the file with the given mode.
83    pub fn open(path: &CStr, mode: FileOpenMode) -> Result<File, ()> {
84        let handle = unsafe { syscall!(OPEN, path.as_ptr(), mode, path.to_bytes().len()) } as isize;
85
86        if handle == -1 {
87            return Err(());
88        }
89
90        Ok(File { handle })
91    }
92
93    /// Tries to write all of buffers bytes into the file, and returns the number of bytes that were actually written.
94    pub fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
95        let not_written = unsafe { syscall!(WRITE, self.handle, buf.as_ptr(), buf.len()) };
96
97        if not_written > buf.len() {
98            return Err(());
99        }
100
101        Ok(buf.len() - not_written)
102    }
103
104    /// Closes the file.
105    ///
106    /// The file is also closed when it the file is dropped.
107    /// However this method is more explicit and allows to check if an error happenend while closing the file.
108    ///
109    /// If an error occured, the unclosed file is returned.
110    pub fn close(self) -> Result<(), File> {
111        match self.close_internal() {
112            Ok(()) => {
113                // Drop should not be called again,
114                // because the file would be closed twice
115                mem::forget(self);
116
117                Ok(())
118            }
119            Err(()) => Err(self),
120        }
121    }
122
123    fn close_internal(&self) -> Result<(), ()> {
124        let success = unsafe { syscall!(CLOSE, self.handle) };
125
126        if success != 0 {
127            return Err(());
128        }
129
130        Ok(())
131    }
132
133    /// Retrieves the total number of bytes of the file
134    #[allow(clippy::len_without_is_empty)]
135    pub fn len(&self) -> Result<usize, ()> {
136        let length = unsafe { syscall!(FLEN, self.handle) };
137
138        if (length as isize) < 0 {
139            return Err(());
140        }
141
142        Ok(length)
143    }
144
145    /// Tries to read `buf.len()` bytes from the file and returns the number of bytes that was read into the buffer.
146    ///
147    /// The result `Ok(0usize)` suggests that EOF has been reached.
148    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
149        let not_read = unsafe { syscall!(READ, self.handle, buf.as_mut_ptr(), buf.len()) };
150
151        if not_read > buf.len() {
152            return Err(());
153        }
154
155        Ok(buf.len() - not_read)
156    }
157
158    /// Sets the read/write-cursor to the specified position
159    /// This actually consists of two semihosting operations:
160    /// One to get the length, and another to set the cursor
161    /// 
162    /// If you want to set the cursor to the beginning of the file, use [`rewind`](File::rewind) instead.
163    /// 
164    /// see also: [`seek_unchecked`](File::seek_unchecked)
165    pub fn seek(&mut self, from: SeekFrom) -> Result<(), SeekError> {
166        let length = self.len().map_err(|()| SeekError::Unknown)?;
167
168        let pos = match from {
169            SeekFrom::Start(offset) => offset,
170            SeekFrom::End(offset) => length.wrapping_add(offset as usize),
171        };
172
173        if pos > length {
174            return Err(SeekError::PositionOutOfBounds);
175        }
176
177        // Safety: pos has been checked, it contains a valid value
178        let result = unsafe { self.seek_unchecked(pos) };
179
180        result.map_err(|()| SeekError::Unknown)
181    }
182
183    /// Sets the position of the cursor.
184    ///
185    /// The position is specified relative to the beginning of the file.
186    /// 
187    /// See also [`seek`](File::seek) for a safe, and slightly more ergonomic way to set the cursor.
188    ///
189    /// # Safety
190    ///
191    /// The position must lie inside the extend of the file.
192    /// Otherwise, the behaviour is undefined.
193    pub unsafe fn seek_unchecked(&mut self, pos: usize) -> Result<(), ()> {
194        let result = syscall!(SEEK, self.handle, pos) as isize;
195
196        if result < 0 {
197            return Err(());
198        }
199
200        Ok(())
201    }
202
203    /// Sets the cursor to the beginning of the file.
204    /// 
205    /// In comparison to [`seek`](File::seek), this method does not need to check the length of the file or the provided index.
206    pub fn rewind(&mut self) -> Result<(), ()> {
207        // Safety: 0 is always a valid position
208        unsafe { self.seek_unchecked(0) }
209    }
210}
211
212impl Drop for File {
213    fn drop(&mut self) {
214        // Errors are ignored, just like in std::fs::File
215        let _ = self.close_internal();
216    }
217}