oc_wasm_opencomputers/
filesystem.rs

1//! Provides high-level access to the filesystem APIs.
2
3use crate::error::Error;
4use crate::helpers::{max_usize, Ignore};
5use alloc::vec::Vec;
6use minicbor::{Decode, Encode};
7use oc_wasm_futures::invoke::{component_method, Buffer};
8use oc_wasm_helpers::Lockable;
9use oc_wasm_safe::{
10	component::{Invoker, MethodCallError},
11	descriptor, extref, Address,
12};
13
14/// The type name for filesystem components.
15pub const TYPE: &str = "filesystem";
16
17/// A filesystem component.
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct Filesystem(Address);
20
21impl Filesystem {
22	/// Creates a wrapper around a filesystem.
23	///
24	/// The `address` parameter is the address of the filesystem. It is not checked for correctness
25	/// at this time because network topology could change after this function returns; as such,
26	/// each usage of the value may fail instead.
27	#[must_use = "This function is only useful for its return value"]
28	pub fn new(address: Address) -> Self {
29		Self(address)
30	}
31
32	/// Returns the address of the filesystem.
33	#[must_use = "This function is only useful for its return value"]
34	pub fn address(&self) -> &Address {
35		&self.0
36	}
37}
38
39impl<'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B> for Filesystem {
40	type Locked = Locked<'invoker, 'buffer, B>;
41
42	fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
43		Locked {
44			address: self.0,
45			invoker,
46			buffer,
47		}
48	}
49}
50
51/// A filesystem component on which methods can be invoked.
52///
53/// This type combines a filesystem address, an [`Invoker`] that can be used to make method calls,
54/// and a scratch buffer used to perform CBOR encoding and decoding. A value of this type can be
55/// created by calling [`Filesystem::lock`], and it can be dropped to return the borrow of the
56/// invoker and buffer to the caller so they can be reused for other purposes.
57///
58/// The `'invoker` lifetime is the lifetime of the invoker. The `'buffer` lifetime is the lifetime
59/// of the buffer. The `B` type is the type of scratch buffer to use.
60pub struct Locked<'invoker, 'buffer, B: Buffer> {
61	/// The component address.
62	address: Address,
63
64	/// The invoker.
65	invoker: &'invoker mut Invoker,
66
67	/// The buffer.
68	buffer: &'buffer mut B,
69}
70
71impl<'invoker, 'buffer, B: Buffer> Locked<'invoker, 'buffer, B> {
72	/// Returns the filesystem’s label, if it has one.
73	///
74	/// The returned string slice points into, and therefore retains ownership of, the scratch
75	/// buffer. Consequently, the `Locked` is consumed and cannot be reused.
76	///
77	/// # Errors
78	/// * [`BadComponent`](Error::BadComponent)
79	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
80	pub async fn get_label(self) -> Result<Option<&'buffer str>, Error> {
81		let ret: (Option<&'buffer str>,) = component_method::<(), _, _>(
82			self.invoker,
83			self.buffer,
84			&self.address,
85			"getLabel",
86			None,
87		)
88		.await?;
89		Ok(ret.0)
90	}
91
92	/// Sets or clears the filesystem’s label and returns the new label, which may be truncated.
93	///
94	/// The returned string slice points into, and therefore retains ownership of, the scratch
95	/// buffer. Consequently, the `Locked` is consumed and cannot be reused.
96	///
97	/// # Errors
98	/// * [`BadComponent`](Error::BadComponent)
99	/// * [`StorageReadOnly`](Error::StorageReadOnly) is returned if this filesystem has a label
100	///   that cannot be changed.
101	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
102	/// * [`Unsupported`](Error::Unsupported) is returned if this filesystem does not support
103	///   labels.
104	pub async fn set_label(self, label: Option<&str>) -> Result<Option<&'buffer str>, Error> {
105		// SAFETY: component_method() both encodes and submits the CBOR in one go.
106		let label = label.map(|l| unsafe { extref::String::new(l) });
107		let ret: Result<(Option<&'buffer str>,), MethodCallError<'_>> = component_method(
108			self.invoker,
109			self.buffer,
110			&self.address,
111			"setLabel",
112			Some(&(label,)),
113		)
114		.await;
115		match ret {
116			Ok((label,)) => Ok(label),
117			Err(MethodCallError::BadParameters(_)) => Err(Error::StorageReadOnly),
118			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
119			Err(MethodCallError::Other(_)) => Err(Error::Unsupported),
120			Err(e) => Err(Error::BadComponent(e.into())),
121		}
122	}
123
124	/// Returns whether the filesystem is read-only.
125	///
126	/// # Errors
127	/// * [`BadComponent`](Error::BadComponent)
128	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
129	pub async fn is_read_only(&mut self) -> Result<bool, Error> {
130		let ret: (bool,) = component_method::<(), _, _>(
131			self.invoker,
132			self.buffer,
133			&self.address,
134			"isReadOnly",
135			None,
136		)
137		.await?;
138		Ok(ret.0)
139	}
140
141	/// Returns the total capacity of the filesystem, in bytes.
142	///
143	/// # Errors
144	/// * [`BadComponent`](Error::BadComponent)
145	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
146	pub async fn get_space_total(&mut self) -> Result<u64, Error> {
147		// The component call returns a u64 if the filesystem has a capacity limit, or an f64 equal
148		// to infinity if it doesn’t.
149		enum Return {
150			Finite(u64),
151			Infinite,
152		}
153		use minicbor::{data::Type, Decoder};
154		impl<Context> Decode<'_, Context> for Return {
155			fn decode(
156				d: &mut Decoder<'_>,
157				_: &mut Context,
158			) -> Result<Self, minicbor::decode::Error> {
159				match d.datatype()? {
160					Type::F16 | Type::F32 | Type::F64 => {
161						d.skip()?;
162						Ok(Return::Infinite)
163					}
164					_ => Ok(Return::Finite(d.u64()?)),
165				}
166			}
167		}
168		let ret: (Return,) = component_method::<(), _, _>(
169			self.invoker,
170			self.buffer,
171			&self.address,
172			"spaceTotal",
173			None,
174		)
175		.await?;
176		Ok(match ret.0 {
177			Return::Finite(x) => x,
178			Return::Infinite => u64::MAX,
179		})
180	}
181
182	/// Returns the size of all files the filesystem, in bytes.
183	///
184	/// # Errors
185	/// * [`BadComponent`](Error::BadComponent)
186	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
187	pub async fn get_space_used(&mut self) -> Result<u64, Error> {
188		let ret: (u64,) = component_method::<(), _, _>(
189			self.invoker,
190			self.buffer,
191			&self.address,
192			"spaceUsed",
193			None,
194		)
195		.await?;
196		Ok(ret.0)
197	}
198
199	/// Returns whether a file or directory of the given name exists.
200	///
201	/// # Errors
202	/// * [`BadComponent`](Error::BadComponent)
203	/// * [`BadFilename`](Error::BadFilename)
204	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
205	pub async fn exists(&mut self, path: &str) -> Result<bool, Error> {
206		self.call_path_to_value("exists", path).await
207	}
208
209	/// Returns the size of a file.
210	///
211	/// If the path does not exist or is a directory, zero is returned.
212	///
213	/// # Errors
214	/// * [`BadComponent`](Error::BadComponent)
215	/// * [`BadFilename`](Error::BadFilename)
216	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
217	pub async fn size(&mut self, path: &str) -> Result<u64, Error> {
218		self.call_path_to_value("size", path).await
219	}
220
221	/// Returns whether a path refers to an existing directory.
222	///
223	/// # Errors
224	/// * [`BadComponent`](Error::BadComponent)
225	/// * [`BadFilename`](Error::BadFilename)
226	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
227	pub async fn is_directory(&mut self, path: &str) -> Result<bool, Error> {
228		self.call_path_to_value("isDirectory", path).await
229	}
230
231	/// Returns the last modification time of a file, or the creation time of a directory, in
232	/// milliseconds since the UNIX epoch.
233	///
234	/// If the path does not exist, zero is returned. Zero may also be returned on certain
235	/// filesystems which do not track modification times, such as virtual filesystems.
236	///
237	/// # Errors
238	/// * [`BadComponent`](Error::BadComponent)
239	/// * [`BadFilename`](Error::BadFilename)
240	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
241	pub async fn last_modified(&mut self, path: &str) -> Result<u64, Error> {
242		self.call_path_to_value("lastModified", path).await
243	}
244
245	/// Returns the objects contained within a directory.
246	///
247	/// If the name refers to a file rather than a directory, a single entry is returned specifying
248	/// the file itself.
249	///
250	/// The returned string slices point into, and therefore retain ownership of, the scratch
251	/// buffer. Consequently, the `Locked` is consumed and cannot be reused.
252	///
253	/// # Errors
254	/// * [`BadComponent`](Error::BadComponent)
255	/// * [`FileNotFound`](Error::FileNotFound)
256	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
257	pub async fn list(self, path: &str) -> Result<Vec<DirectoryEntry<'buffer>>, Error> {
258		// SAFETY: component_method() both encodes and submits the CBOR in one go.
259		let path = unsafe { extref::String::new(path) };
260		let ret: Result<(Option<Vec<DirectoryEntry<'buffer>>>,), _> = component_method(
261			self.invoker,
262			self.buffer,
263			&self.address,
264			"list",
265			Some(&(path,)),
266		)
267		.await;
268		match ret {
269			Ok((Some(listing),)) => Ok(listing),
270			Ok((None,)) | Err(MethodCallError::Other(_)) => Err(Error::FileNotFound),
271			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
272			Err(e) => Err(Error::BadComponent(e.into())),
273		}
274	}
275
276	/// Creates a directory and, if missing, its parents.
277	///
278	/// # Errors
279	/// * [`BadComponent`](Error::BadComponent)
280	/// * [`BadFilename`](Error::BadFilename)
281	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
282	/// * [`Failed`](Error::Failed) is returned if the target directory already exists, or if a
283	///   directory on the path cannot be created (typically due to lack of space, an intermediate
284	///   path component being an existing file, or the filesystem being read-only).
285	pub async fn make_directory(&mut self, path: &str) -> Result<(), Error> {
286		let ret: bool = self.call_path_to_value("makeDirectory", path).await?;
287		if ret {
288			Ok(())
289		} else {
290			Err(Error::Failed)
291		}
292	}
293
294	/// Removes a file or directory and its contents.
295	///
296	/// # Errors
297	/// * [`BadComponent`](Error::BadComponent)
298	/// * [`BadFilename`](Error::BadFilename)
299	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
300	/// * [`Failed`](Error::Failed) is returned if the removal fails (typically due to the path not
301	///   existing or the filesystem being read-only).
302	pub async fn remove(&mut self, path: &str) -> Result<(), Error> {
303		let removed: bool = self.call_path_to_value("remove", path).await?;
304		if removed {
305			Ok(())
306		} else {
307			Err(Error::Failed)
308		}
309	}
310
311	/// Renames a file or directory.
312	///
313	/// # Errors
314	/// * [`BadComponent`](Error::BadComponent)
315	/// * [`BadFilename`](Error::BadFilename)
316	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
317	/// * [`Failed`](Error::Failed) is returned if the rename fails (typically due to the source
318	///   path not existing, the destination path existing and being of a different type than the
319	///   source, the destination being a non-empty directory, or the filesystem being read-only).
320	pub async fn rename(&mut self, source: &str, destination: &str) -> Result<(), Error> {
321		// SAFETY: component_method() both encodes and submits the CBOR in one go.
322		let source = unsafe { extref::String::new(source) };
323		let destination = unsafe { extref::String::new(destination) };
324		let ret: Result<(bool,), _> = component_method(
325			self.invoker,
326			self.buffer,
327			&self.address,
328			"rename",
329			Some(&(source, destination)),
330		)
331		.await;
332		match ret {
333			Ok((true,)) => Ok(()),
334			Ok((false,)) => Err(Error::Failed),
335			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
336			Err(MethodCallError::Other(_)) => Err(Error::BadFilename),
337			Err(e) => Err(e.into()),
338		}
339	}
340
341	/// Opens a file in read mode.
342	///
343	/// # Errors
344	/// * [`BadComponent`](Error::BadComponent)
345	/// * [`FileNotFound`](Error::FileNotFound)
346	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
347	pub async fn open_read(&mut self, path: &str) -> Result<ReadHandle, Error> {
348		Ok(ReadHandle {
349			address: self.address,
350			descriptor: self.call_open(path, "r", Error::FileNotFound).await?,
351		})
352	}
353
354	/// Opens a file in write mode.
355	///
356	/// # Errors
357	/// * [`BadComponent`](Error::BadComponent) is returned if the filesystem does not exist, is
358	///   inaccessible, or is not a filesystem.
359	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
360	/// * [`Failed`](Error::Failed) is returned if the path is a directory, the parent of the path
361	///   is not a directory, or the filesystem is read-only.
362	pub async fn open_write(&mut self, path: &str, mode: WriteMode) -> Result<WriteHandle, Error> {
363		Ok(WriteHandle {
364			address: self.address,
365			descriptor: self.call_open(path, mode.as_str(), Error::Failed).await?,
366		})
367	}
368
369	/// Performs a method call that takes a path as its only parameter and returns a single value
370	/// that does not borrow from the buffer.
371	///
372	/// # Errors
373	/// * [`BadComponent`](Error::BadComponent)
374	/// * [`BadFilename`](Error::BadFilename)
375	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
376	async fn call_path_to_value<T>(&mut self, method: &str, path: &str) -> Result<T, Error>
377	where
378		for<'a> T: Decode<'a, ()>,
379	{
380		// SAFETY: component_method() both encodes and submits the CBOR in one go.
381		let path = unsafe { extref::String::new(path) };
382		let ret: Result<(T,), _> = component_method(
383			self.invoker,
384			self.buffer,
385			&self.address,
386			method,
387			Some(&(path,)),
388		)
389		.await;
390		match ret {
391			Ok((v,)) => Ok(v),
392			Err(MethodCallError::Other(_)) => Err(Error::BadFilename),
393			Err(e) => Err(e.into()),
394		}
395	}
396
397	/// Performs an `open` method call.
398	///
399	/// # Errors
400	/// * [`BadComponent`](Error::BadComponent)
401	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
402	/// * The error passed in `file_not_found_error`
403	async fn call_open(
404		&mut self,
405		path: &str,
406		mode: &str,
407		file_not_found_error: Error,
408	) -> Result<descriptor::Owned, Error> {
409		// SAFETY: component_method() both encodes and submits the CBOR in one go.
410		let path = unsafe { extref::String::new(path) };
411		let descriptor: Result<(descriptor::Decoded,), MethodCallError<'_>> = component_method(
412			self.invoker,
413			self.buffer,
414			&self.address,
415			"open",
416			Some(&(path, mode)),
417		)
418		.await;
419		match descriptor {
420			Ok((descriptor,)) => Ok(
421				// SAFETY: This descriptor was just generated by the open() method call, so it must
422				// be fresh and unique.
423				unsafe { descriptor.into_owned() },
424			),
425			Err(MethodCallError::Other(exception)) => {
426				if exception.is_type("java.io.FileNotFoundException") {
427					// FileNotFoundException is raised for almost everything.
428					Err(file_not_found_error)
429				} else if exception.is_type("java.io.IOException") {
430					// There appears to be only one non-FileNotFoundException IOException, which is
431					// too many open handles. Just in case, check the message.
432					const TOO_MANY_OPEN_HANDLES: &str = "too many open handles";
433					const ERROR_MESSAGE_BUFFER_SIZE: usize = TOO_MANY_OPEN_HANDLES.len();
434					let mut message_buffer = [0_u8; ERROR_MESSAGE_BUFFER_SIZE];
435					match exception.message(&mut message_buffer) {
436						Ok(TOO_MANY_OPEN_HANDLES) => Err(Error::TooManyDescriptors),
437						_ => Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown)),
438					}
439				} else {
440					// No other exceptions appear to be thrown, so if one is, it means we’re
441					// talking to something that’s not a normal filesystem.
442					Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
443				}
444			}
445			Err(e) => Err(e.into()),
446		}
447	}
448}
449
450/// The type of a directory entry.
451#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
452pub enum DirectoryEntryType {
453	/// The object is a file.
454	File,
455
456	/// The object is a directory.
457	Directory,
458}
459
460/// A directory entry.
461#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
462pub struct DirectoryEntry<'buffer> {
463	/// The name of the object.
464	pub name: &'buffer str,
465
466	/// The type of the object.
467	pub object_type: DirectoryEntryType,
468}
469
470impl<'buffer, Context> Decode<'buffer, Context> for DirectoryEntry<'buffer> {
471	fn decode(
472		d: &mut minicbor::Decoder<'buffer>,
473		_: &mut Context,
474	) -> Result<Self, minicbor::decode::Error> {
475		let name = d.str()?;
476		Ok(match name.strip_suffix('/') {
477			Some(name) => DirectoryEntry {
478				name,
479				object_type: DirectoryEntryType::Directory,
480			},
481			None => DirectoryEntry {
482				name,
483				object_type: DirectoryEntryType::File,
484			},
485		})
486	}
487}
488
489/// The possible ways to open a file for writing.
490#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
491pub enum WriteMode {
492	/// If the file does not exist, creates it as an empty file. If it does exist, deletes its
493	/// contents. In either case, returns a handle with initial position zero.
494	Truncate,
495
496	/// If the file does not exist, creates it as an empty file. If it does exist, leaves its
497	/// contents intact. In either case, returns a handle initially positioned at the end of the
498	/// file.
499	Append,
500}
501
502impl WriteMode {
503	fn as_str(self) -> &'static str {
504		match self {
505			Self::Truncate => "w",
506			Self::Append => "a",
507		}
508	}
509}
510
511/// The possible ways to seek within a file.
512#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
513pub enum Seek {
514	/// Seeks to an absolute position.
515	Set,
516
517	/// Seeks to a position relative to the current position.
518	Current,
519
520	/// Seeks to a position relative to the end of the file.
521	End,
522}
523
524impl<Context> Encode<Context> for Seek {
525	fn encode<W: minicbor::encode::Write>(
526		&self,
527		e: &mut minicbor::Encoder<W>,
528		_: &mut Context,
529	) -> Result<(), minicbor::encode::Error<W::Error>> {
530		e.str(match self {
531			Self::Set => "set",
532			Self::Current => "cur",
533			Self::End => "end",
534		})?;
535		Ok(())
536	}
537}
538
539/// A handle to a file open for reading.
540///
541/// The file is closed when the handle is dropped.
542#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
543pub struct ReadHandle {
544	/// The filesystem component address.
545	address: Address,
546
547	/// The opaque value descriptor.
548	descriptor: descriptor::Owned,
549}
550
551impl<'handle, 'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B>
552	for &'handle ReadHandle
553{
554	type Locked = LockedReadHandle<'handle, 'invoker, 'buffer, B>;
555
556	fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
557		LockedReadHandle {
558			handle: self,
559			invoker,
560			buffer,
561		}
562	}
563}
564
565/// A readable file handle on which methods can be invoked.
566///
567/// This type combines a readable file handle, an [`Invoker`] that can be used to make method
568/// calls, and a scratch buffer used to perform CBOR encoding and decoding. A value of this type
569/// can be created by calling [`ReadHandle::lock`](Lockable::lock), and it can be dropped to return
570/// the borrow of the invoker and buffer to the caller so they can be reused for other purposes.
571///
572/// The `'handle` lifetime is the lifetime of the original file handle. The `'invoker` lifetime is
573/// the lifetime of the invoker. The `'buffer` lifetime is the lifetime of the buffer. The `B` type
574/// is the type of scratch buffer to use.
575pub struct LockedReadHandle<'handle, 'invoker, 'buffer, B: Buffer> {
576	/// The file handle.
577	handle: &'handle ReadHandle,
578
579	/// The invoker.
580	invoker: &'invoker mut Invoker,
581
582	/// The buffer.
583	buffer: &'buffer mut B,
584}
585
586impl<'handle, 'invoker, 'buffer, B: Buffer> LockedReadHandle<'handle, 'invoker, 'buffer, B> {
587	/// Seeks to a position in the file and returns the resulting absolute byte position.
588	///
589	/// # Errors
590	/// * [`BadComponent`](Error::BadComponent)
591	/// * [`NegativeSeek`](Error::NegativeSeek)
592	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
593	pub async fn seek(&mut self, basis: Seek, offset: i64) -> Result<u64, Error> {
594		seek_impl(
595			self.invoker,
596			self.buffer,
597			&self.handle.address,
598			&self.handle.descriptor,
599			basis,
600			offset,
601		)
602		.await
603	}
604
605	/// Reads bytes from the file.
606	///
607	/// `None` is returned if no bytes were read because the handle’s initial position was at or
608	/// beyond EOF.
609	///
610	/// The returned byte slice points into, and therefore retains ownership of, the scratch
611	/// buffer. Consequently, the `LockedReadHandle` is consumed and cannot be reused.
612	///
613	/// # Errors
614	/// * [`BadComponent`](Error::BadComponent)
615	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
616	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
617	pub async fn read(self, length: usize) -> Result<Option<&'buffer [u8]>, Error> {
618		use minicbor::bytes::ByteSlice;
619		#[derive(Encode)]
620		#[cbor(array)]
621		struct Params<'descriptor>(#[n(0)] &'descriptor descriptor::Owned, #[n(1)] usize);
622		let ret: Result<(Option<&'buffer ByteSlice>,), MethodCallError<'_>> = component_method(
623			self.invoker,
624			self.buffer,
625			&self.handle.address,
626			"read",
627			Some(&Params(&self.handle.descriptor, length)),
628		)
629		.await;
630		match ret {
631			Ok((Some(bytes),)) => Ok(Some(bytes)),
632			Ok((None,)) => Ok(None),
633			Err(MethodCallError::Other(exception)) => {
634				if exception.is_type("java.io.IOException") {
635					// The borrow checker isn’t quite smart enough to notice that we can drop the
636					// borrow of “buffer” and reuse it in this branch because we’re not going to
637					// return anything that depends on it. Fortunately, the error string we’re
638					// looking for is small and of fixed size, so just use a fixed-size buffer
639					// instead.
640					const NOT_ENOUGH_ENERGY: &str = "not enough energy";
641					let mut message_buffer = [0_u8; NOT_ENOUGH_ENERGY.len()];
642					match exception.message(&mut message_buffer) {
643						Ok(m) if m == NOT_ENOUGH_ENERGY => Err(Error::NotEnoughEnergy),
644						_ => Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown)),
645					}
646				} else {
647					Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
648				}
649			}
650			Err(e) => Err(e.into()),
651		}
652	}
653}
654
655/// A handle to a file open for reading.
656///
657/// The file is closed when the handle is dropped.
658#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
659pub struct WriteHandle {
660	/// The filesystem component address.
661	address: Address,
662
663	/// The opaque value descriptor.
664	descriptor: descriptor::Owned,
665}
666
667impl<'handle, 'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B>
668	for &'handle WriteHandle
669{
670	type Locked = LockedWriteHandle<'handle, 'invoker, 'buffer, B>;
671
672	fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
673		LockedWriteHandle {
674			handle: self,
675			invoker,
676			buffer,
677		}
678	}
679}
680
681/// A writeable file handle on which methods can be invoked.
682///
683/// This type combines a writeable file handle, an [`Invoker`] that can be used to make method
684/// calls, and a scratch buffer used to perform CBOR encoding and decoding. A value of this type
685/// can be created by calling [`WriteHandle::lock`](Lockable::lock), and it can be dropped to
686/// return the borrow of the invoker and buffer to the caller so they can be reused for other
687/// purposes.
688///
689/// The `'handle` lifetime is the lifetime of the original file handle. The `'invoker` lifetime is
690/// the lifetime of the invoker. The `'buffer` lifetime is the lifetime of the buffer. The `B` type
691/// is the type of scratch buffer to use.
692pub struct LockedWriteHandle<'handle, 'invoker, 'buffer, B: Buffer> {
693	/// The file handle.
694	handle: &'handle WriteHandle,
695
696	/// The invoker.
697	invoker: &'invoker mut Invoker,
698
699	/// The buffer.
700	buffer: &'buffer mut B,
701}
702
703impl<'handle, 'invoker, 'buffer, B: Buffer> LockedWriteHandle<'handle, 'invoker, 'buffer, B> {
704	/// Seeks to a position in the file and returns the resulting absolute byte position.
705	///
706	/// # Errors
707	/// * [`BadComponent`](Error::BadComponent)
708	/// * [`NegativeSeek`](Error::NegativeSeek)
709	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
710	pub async fn seek(&mut self, basis: Seek, offset: i64) -> Result<u64, Error> {
711		seek_impl(
712			self.invoker,
713			self.buffer,
714			&self.handle.address,
715			&self.handle.descriptor,
716			basis,
717			offset,
718		)
719		.await
720	}
721
722	/// Writes bytes to the file.
723	///
724	/// # Errors
725	/// * [`BadComponent`](Error::BadComponent)
726	/// * [`DataTooLarge`](Error::DataTooLarge) if the disk is out of space
727	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
728	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
729	pub async fn write(&mut self, bytes: &[u8]) -> Result<(), Error> {
730		#[derive(Encode)]
731		#[cbor(array)]
732		struct Params<'descriptor, 'bytes>(
733			#[n(0)] &'descriptor descriptor::Owned,
734			#[n(1)] extref::Bytes<'bytes>,
735		);
736		// SAFETY: component_method() both encodes and submits the CBOR in one go.
737		let bytes = unsafe { extref::Bytes::new(bytes) };
738		let ret: Result<Ignore, MethodCallError<'_>> = component_method(
739			self.invoker,
740			self.buffer,
741			&self.handle.address,
742			"write",
743			Some(&Params(&self.handle.descriptor, bytes)),
744		)
745		.await;
746		match ret {
747			Ok(_) => Ok(()),
748			Err(MethodCallError::Other(exception)) => {
749				if exception.is_type("java.io.IOException") {
750					// The borrow checker isn’t quite smart enough to notice that we can drop the
751					// borrow of “buffer” and reuse it in this branch because we’re not going to
752					// return anything that depends on it. Fortunately, the error string we’re
753					// looking for is small and of fixed size, so just use a fixed-size buffer
754					// instead.
755					const NOT_ENOUGH_ENERGY: &str = "not enough energy";
756					const NOT_ENOUGH_SPACE: &str = "not enough space";
757					let mut message_buffer =
758						[0_u8; max_usize(NOT_ENOUGH_ENERGY.len(), NOT_ENOUGH_SPACE.len())];
759					match exception.message(&mut message_buffer) {
760						Ok(m) if m == NOT_ENOUGH_ENERGY => Err(Error::NotEnoughEnergy),
761						Ok(m) if m == NOT_ENOUGH_SPACE => Err(Error::DataTooLarge),
762						_ => Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown)),
763					}
764				} else {
765					Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
766				}
767			}
768			Err(e) => Err(e.into()),
769		}
770	}
771}
772
773/// Seeks to a position in a file and returns the resulting absolute byte position.
774///
775/// # Errors
776/// * [`BadComponent`](Error::BadComponent)
777/// * [`NegativeSeek`](Error::NegativeSeek)
778/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
779async fn seek_impl<B: Buffer>(
780	invoker: &mut Invoker,
781	buffer: &mut B,
782	address: &Address,
783	descriptor: &descriptor::Owned,
784	basis: Seek,
785	offset: i64,
786) -> Result<u64, Error> {
787	#[derive(Encode)]
788	#[cbor(array)]
789	struct Params<'descriptor>(
790		#[n(0)] &'descriptor descriptor::Owned,
791		#[n(1)] Seek,
792		#[n(2)] i64,
793	);
794	let ret: Result<(u64,), MethodCallError<'_>> = component_method(
795		invoker,
796		buffer,
797		address,
798		"seek",
799		Some(&Params(descriptor, basis, offset)),
800	)
801	.await;
802	match ret {
803		Ok((position,)) => Ok(position),
804		Err(MethodCallError::BadParameters(_)) => Err(Error::NegativeSeek),
805		Err(e) => Err(e.into()),
806	}
807}