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}