1use crate::{Fd, FileOperand, VfOffset};
2use std::path::Path;
3
4#[bitfields::bitfield(u8)]
5struct NativeWriteFlags {
6 creation: bool,
7 truncate: bool,
8 #[bits(6)]
9 _reserved: u8,
10}
11
12pub type WriteOp<T = FileOperand, D = Vec<u8>> = WriteRequest<T, D, VfOffset, u8>;
25
26#[derive(Clone, Copy)]
37pub struct WriteRequest<T, D, O, P> {
38 file: T,
39 offset: O,
40 data: D,
41 flags: P,
42}
43
44impl<T, D, O, P: Default> WriteRequest<T, D, O, P> {
45 pub fn new(file: T, offset: O, data: D) -> Self {
47 Self {
48 file,
49 offset,
50 data,
51 flags: P::default(),
52 }
53 }
54}
55
56impl<T, D> WriteRequest<T, D, VfOffset, u8> {
57 pub fn creates(&self) -> bool {
59 NativeWriteFlags::from_bits(self.flags).creation()
60 }
61 pub fn truncates(&self) -> bool {
63 NativeWriteFlags::from_bits(self.flags).truncate()
64 }
65 pub fn with_creation(mut self) -> Self {
67 let mut flags = NativeWriteFlags::from_bits(self.flags);
68 flags.set_creation(true);
69 self.flags = flags.into_bits();
70 self
71 }
72 pub fn with_truncate(mut self) -> Self {
74 let mut flags = NativeWriteFlags::from_bits(self.flags);
75 flags.set_truncate(true);
76 self.flags = flags.into_bits();
77 self
78 }
79}
80
81impl<T, D: AsRef<[u8]>, O: Copy, P: Copy> WriteRequest<T, D, O, P> {
82 pub fn borrowed(&self) -> WriteRequest<&T, &[u8], O, P> {
84 WriteRequest {
85 file: &self.file,
86 offset: self.offset,
87 data: self.data.as_ref(),
88 flags: self.flags,
89 }
90 }
91}
92
93impl<T, D, O: Copy, P> WriteRequest<T, D, O, P> {
94 pub fn offset(&self) -> O {
96 self.offset
97 }
98}
99
100impl<D, O, P> WriteRequest<FileOperand, D, O, P> {
101 pub fn file(&self) -> &FileOperand {
103 &self.file
104 }
105}
106impl<T, O, P> WriteRequest<T, Vec<u8>, O, P> {
107 pub fn data(&self) -> &[u8] {
109 &self.data
110 }
111}
112
113impl<'a, T, O, P> WriteRequest<&'a T, &'a [u8], O, P> {
114 pub fn file(&self) -> &'a T {
116 self.file
117 }
118 pub fn data(&self) -> &'a [u8] {
120 self.data
121 }
122}
123
124impl<T, D, O: From<u64>, P: Default> WriteRequest<T, D, O, P> {
125 pub fn at(file: T, offset: u64, data: D) -> Self {
141 Self::new(file, offset.into(), data)
142 }
143}
144
145impl WriteOp {
146 pub fn from_path(path: &str, offset: VfOffset, data: Vec<u8>) -> Self {
147 Self::new(FileOperand::from_path(path), offset, data)
148 }
149 pub fn from_os_path(path: &Path, offset: VfOffset, data: Vec<u8>) -> Self {
150 Self::new(FileOperand::from_os_path(path), offset, data)
151 }
152 pub fn from_fd(fd: Fd, offset: VfOffset, data: Vec<u8>) -> Self {
153 Self::new(FileOperand::from_fd(fd), offset, data)
154 }
155}
156
157impl<T, D: AsRef<[u8]>, O: std::fmt::Debug, P> std::fmt::Debug for WriteRequest<T, D, O, P> {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 f.debug_struct("WriteOp")
160 .field("offset", &self.offset)
161 .field("length", &self.data.as_ref().len())
162 .finish_non_exhaustive()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn owned_storage_is_borrowed_without_copying_or_losing_native_options() {
172 let file = FileOperand::from_os_path(Path::new("/output"));
173 for offset in [VfOffset::At(7), VfOffset::Cur, VfOffset::End] {
174 for (create, truncate) in [(false, false), (true, false), (false, true), (true, true)] {
175 let mut op = WriteOp::new(file.clone(), offset, vec![0x5a; 1024]);
176 if create {
177 op = op.with_creation();
178 }
179 if truncate {
180 op = op.with_truncate();
181 }
182 let native: WriteOp<&FileOperand, &[u8]> = op.borrowed();
183 assert!(std::ptr::eq(native.file(), op.file()));
184 assert_eq!(native.data().as_ptr(), op.data().as_ptr());
185 assert_eq!(native.data().len(), op.data().len());
186 assert_eq!(native.offset(), offset);
187 assert_eq!(native.creates(), create);
188 assert_eq!(native.truncates(), truncate);
189 assert_eq!(op.borrowed().data().as_ptr(), native.data().as_ptr());
191 }
192 }
193 let empty = WriteOp::from_fd(7, VfOffset::Cur, Vec::new());
194 assert!(empty.borrowed().data().is_empty());
195 }
196
197 #[test]
198 fn portable_operations_are_copyable_without_handle_traits_or_extra_flags_storage() {
199 struct NonClone;
200 let file = NonClone;
201 let payload = b"payload";
202 let op = crate::api::WriteOp::at(&file, 11, payload);
203 let copy = op;
204 assert!(std::ptr::eq(op.file(), copy.file()));
205 assert_eq!(op.data().as_ptr(), payload.as_ptr());
206 assert_eq!(copy.data().as_ptr(), payload.as_ptr());
207 assert_eq!(copy.offset(), 11);
208 assert_eq!(
209 std::mem::size_of_val(&op),
210 std::mem::size_of::<(&NonClone, u64, &[u8])>()
211 );
212 assert_eq!(std::mem::size_of::<NativeWriteFlags>(), 1);
213 type HandleOp<'a> = WriteRequest<&'a NonClone, &'a [u8], VfOffset, ()>;
215 assert_eq!(
216 std::mem::size_of::<HandleOp<'_>>(),
217 std::mem::size_of::<(&NonClone, VfOffset, &[u8])>()
218 );
219 assert!(!format!("{op:?}").is_empty());
220 }
221}