1use crate::fuchsia::file::FxFile;
6use crate::fuchsia::fxblob::BlobDirectory;
7use crate::fuchsia::fxblob::blob::FxBlob;
8use crate::fuchsia::node::{FxNode, OpenedNode};
9use crate::fuchsia::pager::PagerBacked;
10use crate::fuchsia::volume::FxVolume;
11use anyhow::{Context as _, Error, anyhow, ensure};
12use arrayref::{array_refs, mut_array_refs};
13use async_trait::async_trait;
14use fuchsia_async as fasync;
15use fuchsia_hash::Hash;
16use futures::future::{self, BoxFuture, join_all};
17use futures::lock::Mutex;
18use futures::{FutureExt, select};
19use fxfs::errors::FxfsError;
20use fxfs::log::*;
21use fxfs::object_handle::{INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle};
22use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
23use fxfs::object_store::{
24 DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore, Timestamp, VOLUME_DATA_KEY_ID,
25 directory,
26};
27use linked_hash_map::LinkedHashMap;
28use scopeguard::ScopeGuard;
29use std::cmp::{Eq, PartialEq};
30use std::collections::btree_map::{BTreeMap, Entry};
31use std::marker::PhantomData;
32use std::mem::size_of;
33use std::pin::pin;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU64, Ordering};
36use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
37use vfs::execution_scope::ActiveGuard;
38
39const FILE_OPEN_MARKER: u64 = u64::MAX;
40const REPLAY_THREADS: usize = 2;
41const MESSAGE_CHUNK_SIZE: usize = 64;
44const IO_SIZE: usize = 1 << 17; pub static RECORDED: AtomicU64 = AtomicU64::new(0);
47
48pub trait RecordingHandle: Send + Sync {
50 fn append<'a>(
52 &'a self,
53 buf: storage_device::buffer::BufferRef<'a>,
54 ) -> futures::future::BoxFuture<'a, Result<u64, Error>>;
55
56 fn allocate_buffer(
57 &self,
58 size: usize,
59 ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>>;
60
61 fn block_size(&self) -> usize;
62
63 fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>>;
65
66 fn abort_cleanup(self: Box<Self>);
69}
70
71pub struct FileRecordingHandle {
73 name: String,
74 volume: Arc<FxVolume>,
75 handle: DataObjectHandle<FxVolume>,
76}
77
78impl FileRecordingHandle {
79 pub async fn new(name: &str, volume: Arc<FxVolume>) -> Result<Self, Error> {
80 let store = volume.store();
81 let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
82 let handle =
83 ObjectStore::create_object(&volume, &mut transaction, HandleOptions::default(), None)
84 .await?;
85 store.add_to_graveyard(&mut transaction, handle.object_id());
86 transaction.commit().await?;
87
88 Ok(Self { name: name.to_string(), volume, handle })
89 }
90
91 async fn commit_impl(&self) -> Result<(), Error> {
92 let store = self.volume.store();
93 let fs = store.filesystem();
94 let profile_dir = self.volume.get_profile_directory().await?;
95
96 let mut lock_keys =
97 lock_keys![LockKey::object(store.store_object_id(), profile_dir.object_id())];
98 let mut old_id = INVALID_OBJECT_ID;
99 let mut transaction = loop {
100 let transaction = store.new_transaction(lock_keys, Options::default()).await?;
101 if let Some((id, descriptor, _)) = profile_dir.lookup(&self.name).await? {
102 ensure!(matches!(descriptor, ObjectDescriptor::File), FxfsError::Inconsistent);
103 if id == old_id {
104 break transaction;
105 }
106 lock_keys = lock_keys![
107 LockKey::object(store.store_object_id(), profile_dir.object_id()),
108 LockKey::object(store.store_object_id(), id)
109 ];
110 old_id = id;
111 } else {
112 old_id = INVALID_OBJECT_ID;
113 break transaction;
114 }
115 };
116
117 store.remove_from_graveyard(&mut transaction, self.handle.object_id());
118 directory::replace_child_with_object(
119 &mut transaction,
120 Some((self.handle.object_id(), ObjectDescriptor::File)),
121 (&profile_dir, &self.name),
122 0,
123 false,
124 Timestamp::now(),
125 )
126 .await?;
127 transaction.commit().await?;
128
129 if old_id != INVALID_OBJECT_ID {
130 fs.graveyard().queue_tombstone_object(store.store_object_id(), old_id);
131 }
132
133 Ok(())
134 }
135}
136
137impl RecordingHandle for FileRecordingHandle {
138 fn append<'a>(
139 &'a self,
140 buf: storage_device::buffer::BufferRef<'a>,
141 ) -> futures::future::BoxFuture<'a, Result<u64, Error>> {
142 async move { self.handle.write_or_append(None, buf).await.map_err(Into::into) }.boxed()
143 }
144
145 fn allocate_buffer(
146 &self,
147 size: usize,
148 ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>> {
149 self.handle.allocate_buffer(size).boxed()
150 }
151
152 fn block_size(&self) -> usize {
153 self.handle.block_size() as usize
154 }
155
156 fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>> {
157 async move {
158 let store = self.volume.store();
159 self.commit_impl().await.inspect_err(|_| {
160 store
161 .filesystem()
162 .graveyard()
163 .queue_tombstone_object(store.store_object_id(), self.handle.object_id());
164 })
165 }
166 .boxed()
167 }
168
169 fn abort_cleanup(self: Box<Self>) {
170 let this = *self;
171 this.volume
172 .store()
173 .filesystem()
174 .graveyard()
175 .queue_tombstone_object(this.volume.store().store_object_id(), this.handle.object_id());
176 }
177}
178
179trait RecordedVolume: Send + Sync + Sized + Unpin {
180 type IdType: std::fmt::Display + Ord + Send + Sized;
181 type NodeType: PagerBacked;
182 type MessageType: Message<IdType = Self::IdType>;
183
184 fn new(volume: Arc<FxVolume>) -> Self;
185
186 fn open(
187 &self,
188 id: Self::IdType,
189 ) -> impl std::future::Future<Output = Result<OpenedNode<Self::NodeType>, Error>> + Send;
190
191 fn file_is_replayable(
193 &self,
194 id: &Self::IdType,
195 ) -> impl std::future::Future<Output = bool> + Send;
196
197 fn read_and_queue(
198 &self,
199 handle: Box<dyn ReadObjectHandle>,
200 sender: &async_channel::Sender<Request<Self::NodeType>>,
201 local_cache: &mut BTreeMap<Self::IdType, Option<OpenedNode<Self::NodeType>>>,
202 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
203 async move {
204 let mut io_buf = handle.allocate_buffer(IO_SIZE).await;
205 let block_size = handle.block_size() as usize;
206 let file_size = handle.get_size() as usize;
207 let mut offset = 0;
208 while offset < file_size {
209 let actual = handle
210 .read(offset as u64, io_buf.as_mut())
211 .await
212 .map_err(|e| e.context(format!("Failed to read at offset: {}", offset)))?;
213 offset += actual;
214 let mut local_offset = 0;
215 let mut next_block = block_size;
216 let mut next_offset = size_of::<Self::MessageType>();
217 while next_offset <= actual {
218 let data = io_buf.subslice(local_offset..next_offset);
219 let msg = Self::MessageType::decode_from(data.as_ptr_slice());
220
221 local_offset = next_offset;
222 next_offset = local_offset + size_of::<Self::MessageType>();
223 if next_offset > next_block {
225 local_offset = next_block;
226 next_offset = local_offset + size_of::<Self::MessageType>();
227 next_block += block_size;
228 }
229
230 if msg.is_zeroes() {
233 break;
234 }
235
236 let file = match local_cache.entry(msg.id()) {
237 Entry::Occupied(entry) => match entry.get() {
238 Some(opened_file) => (*opened_file).clone(),
239 None => continue,
241 },
242 Entry::Vacant(entry) => match self.open(msg.id()).await {
243 Err(e) => {
244 debug!("Failed to open object {} from profile: {:?}", msg.id(), e);
245 entry.insert(None);
247 continue;
248 }
249 Ok(opened_file) => {
250 let file_clone = opened_file.clone();
251 entry.insert(Some(opened_file));
252 file_clone
253 }
254 },
255 };
256
257 sender.send(Request { file, offset: msg.offset() }).await?;
258 }
259 }
260 Ok(())
261 }
262 }
263
264 fn record(
265 &self,
266 recording_handle: Box<dyn RecordingHandle>,
267 receiver: async_channel::Receiver<Vec<Self::MessageType>>,
268 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
269 let recording_handle = scopeguard::guard(recording_handle, |recording_handle| {
271 recording_handle.abort_cleanup();
272 });
273
274 async move {
275 let mut recorded_offsets = LinkedHashMap::<Self::MessageType, ()>::new();
276 let mut recorded_opens = BTreeMap::<Self::IdType, bool>::new();
277 while let Ok(buffer) = receiver.recv().await {
278 for message in buffer {
279 if message.is_open_marker() {
280 if let Entry::Vacant(entry) = recorded_opens.entry(message.id()) {
281 let usable = self.file_is_replayable(entry.key()).await;
282 entry.insert(usable);
283 }
284 } else {
285 recorded_offsets.insert(message, ());
286 }
287 }
288 }
289
290 let block_size = recording_handle.block_size();
291 let mut offset = 0;
292 let mut io_buf = recording_handle.allocate_buffer(IO_SIZE).await;
293 let mut next_block = block_size;
294 while let Some((message, _)) = recorded_offsets.pop_front() {
295 if !recorded_opens.get(&message.id()).copied().unwrap_or(false) {
297 continue;
298 }
299
300 let mut next_offset = offset + size_of::<Self::MessageType>();
301 if next_offset > next_block {
302 io_buf.as_mut().subslice_mut(offset..next_block).fill(0);
305 if next_block >= IO_SIZE {
306 recording_handle
307 .append(io_buf.as_ref())
308 .await
309 .context("Failed to write profile block")?;
310 offset = 0;
311 next_offset = size_of::<Self::MessageType>();
312 next_block = block_size;
313 } else {
314 offset = next_block;
315 next_offset = offset + size_of::<Self::MessageType>();
316 next_block += block_size;
317 }
318 }
319 message.encode_to(
320 io_buf.as_mut().subslice_mut(offset..next_offset).as_mut_ptr_slice(),
321 );
322 offset = next_offset;
323 }
324 if offset > 0 {
325 io_buf.as_mut().subslice_mut(offset..next_block).fill(0);
326 recording_handle
327 .append(io_buf.subslice(0..next_block))
328 .await
329 .context("Failed to write profile block")?;
330 }
331
332 std::mem::drop(io_buf);
333 let recording_handle = ScopeGuard::into_inner(recording_handle);
335 recording_handle.commit().await?;
336
337 Ok(())
338 }
339 }
340}
341
342struct BlobVolume {
343 volume: Arc<FxVolume>,
344 root_dir: Mutex<Option<Arc<BlobDirectory>>>,
347}
348
349impl RecordedVolume for BlobVolume {
350 type IdType = Hash;
351 type NodeType = FxBlob;
352 type MessageType = BlobMessage;
353
354 fn new(volume: Arc<FxVolume>) -> Self {
355 Self { volume, root_dir: Mutex::new(None) }
356 }
357
358 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
359 let mut root_dir = self.root_dir.lock().await;
360 if root_dir.is_none() {
361 *root_dir = Some(
362 self.volume
363 .get_or_load_node(
364 self.volume.store().root_directory_object_id(),
365 ObjectDescriptor::Directory,
366 None,
367 )
368 .await?
369 .into_any()
370 .downcast::<BlobDirectory>()
371 .map_err(|_| FxfsError::Inconsistent)?,
372 );
373 };
374 root_dir
375 .as_ref()
376 .unwrap()
377 .open_blob(&id.into())
378 .await?
379 .ok_or_else(|| FxfsError::NotFound.into())
380 }
381
382 async fn file_is_replayable(&self, _id: &Self::IdType) -> bool {
383 true
385 }
386}
387
388struct FileVolume {
389 volume: Arc<FxVolume>,
390}
391
392impl RecordedVolume for FileVolume {
393 type IdType = u64;
394 type NodeType = FxFile;
395 type MessageType = FileMessage;
396
397 fn new(volume: Arc<FxVolume>) -> Self {
398 Self { volume }
399 }
400
401 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
402 self.volume
403 .get_or_load_node(id, ObjectDescriptor::File, None)
404 .await?
405 .into_any()
406 .downcast::<FxFile>()
407 .map_err(|_| anyhow!("Non-file opened"))?
408 .into_opened_node()
409 .ok_or_else(|| anyhow!("File being purged"))
410 }
411
412 async fn file_is_replayable(&self, id: &Self::IdType) -> bool {
413 match self.volume.store().get_keys(*id).await {
414 Ok(keys)
417 if keys.is_empty()
418 || (keys.len() == 1 && keys.first().unwrap().0 == VOLUME_DATA_KEY_ID) =>
419 {
420 true
421 }
422 _ => false,
423 }
424 }
425}
426
427trait Message: Eq + PartialEq + Sized + Send + Sync + std::hash::Hash + 'static {
428 type IdType: std::fmt::Display + Ord + Send + Sized;
429
430 fn id(&self) -> Self::IdType;
431 fn offset(&self) -> u64;
432 fn encode_to(&self, dest: MutPtrByteSlice<'_>);
433 fn decode_from(src: PtrByteSlice<'_>) -> Self;
434 fn is_zeroes(&self) -> bool;
435 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error>;
436 fn is_open_marker(&self) -> bool;
437}
438
439#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
440struct BlobMessage {
441 id: Hash,
442 offset: u64,
445}
446
447impl BlobMessage {
448 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
449 let (first, second) = mut_array_refs![dest, size_of::<Hash>(), size_of::<u64>()];
450 *first = self.id.into();
451 *second = self.offset.to_le_bytes();
452 }
453
454 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
455 let (first, second) = array_refs!(src, size_of::<Hash>(), size_of::<u64>());
456 Self { id: Hash::from_array(*first), offset: u64::from_le_bytes(*second) }
457 }
458}
459
460impl Message for BlobMessage {
461 type IdType = Hash;
462
463 fn id(&self) -> Self::IdType {
464 self.id
465 }
466
467 fn offset(&self) -> u64 {
468 self.offset
469 }
470
471 fn encode_to(&self, mut dest: MutPtrByteSlice<'_>) {
472 let mut buf = [0u8; size_of::<Self>()];
473 self.encode_to_impl(&mut buf);
474 dest.copy_from_slice(&buf);
475 }
476
477 fn decode_from(src: PtrByteSlice<'_>) -> Self {
478 let mut buf = [0u8; size_of::<Self>()];
479 src.copy_to_slice(&mut buf);
480 Self::decode_from_impl(&buf)
481 }
482
483 fn is_zeroes(&self) -> bool {
484 self.id == Hash::from_array([0u8; size_of::<Hash>()]) && self.offset == 0
485 }
486
487 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
488 match node.into_any().downcast::<FxBlob>() {
489 Ok(blob) => Ok(Self { id: blob.root(), offset }),
490 Err(_) => Err(anyhow!("Cannot record non-blob entry.")),
491 }
492 }
493
494 fn is_open_marker(&self) -> bool {
495 self.offset == FILE_OPEN_MARKER
496 }
497}
498
499#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
500struct FileMessage {
501 id: u64,
502 offset: u64,
505}
506
507impl FileMessage {
508 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
509 let (first, second) = mut_array_refs![dest, size_of::<u64>(), size_of::<u64>()];
510 *first = self.id.to_le_bytes();
511 *second = self.offset.to_le_bytes();
512 }
513
514 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
515 let (first, second) = array_refs!(src, size_of::<u64>(), size_of::<u64>());
516 Self { id: u64::from_le_bytes(*first), offset: u64::from_le_bytes(*second) }
517 }
518}
519
520impl Message for FileMessage {
521 type IdType = u64;
522
523 fn id(&self) -> Self::IdType {
524 self.id
525 }
526
527 fn offset(&self) -> u64 {
528 self.offset
529 }
530
531 fn encode_to(&self, mut dest: MutPtrByteSlice<'_>) {
532 let mut buf = [0u8; size_of::<Self>()];
533 self.encode_to_impl(&mut buf);
534 dest.copy_from_slice(&buf);
535 }
536
537 fn decode_from(src: PtrByteSlice<'_>) -> Self {
538 let mut buf = [0u8; size_of::<Self>()];
539 src.copy_to_slice(&mut buf);
540 Self::decode_from_impl(&buf)
541 }
542
543 fn is_zeroes(&self) -> bool {
544 self.id == 0 && self.offset == 0
545 }
546
547 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
548 match node.into_any().downcast::<FxFile>() {
549 Ok(file) => Ok(Self { id: file.object_id(), offset }),
550 Err(_) => Err(anyhow!("Cannot record non-file entry")),
551 }
552 }
553
554 fn is_open_marker(&self) -> bool {
555 self.offset == FILE_OPEN_MARKER
556 }
557}
558
559pub trait Recorder: Send + Sync {
562 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error>;
564
565 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error>;
567}
568
569struct RecorderImpl<T: Message> {
570 sender: async_channel::Sender<Vec<T>>,
571 buffer: Vec<T>,
572}
573
574impl<T: Message> RecorderImpl<T> {
575 fn new(sender: async_channel::Sender<Vec<T>>) -> Self {
576 Self { sender, buffer: Vec::with_capacity(MESSAGE_CHUNK_SIZE) }
577 }
578}
579
580impl<T: Message> Recorder for RecorderImpl<T> {
581 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error> {
582 self.buffer.push(T::from_node_request(node, offset)?);
583 if self.buffer.len() >= MESSAGE_CHUNK_SIZE {
584 self.sender.try_send(std::mem::replace(
587 &mut self.buffer,
588 Vec::with_capacity(MESSAGE_CHUNK_SIZE),
589 ))?;
590 }
591 RECORDED.fetch_add(1, Ordering::Relaxed);
592 Ok(())
593 }
594
595 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error> {
596 self.record(node, FILE_OPEN_MARKER)
597 }
598}
599
600impl<T: Message> Drop for RecorderImpl<T> {
601 fn drop(&mut self) {
602 if self.buffer.len() > 0 {
604 let buffer = std::mem::take(&mut self.buffer);
605 let _ = self.sender.try_send(buffer);
606 }
607 }
608}
609
610struct Request<P: PagerBacked> {
611 file: Arc<P>,
612 offset: u64,
613}
614
615struct ReplayState<T> {
616 replay_threads: future::Shared<BoxFuture<'static, ()>>,
617 _cache_task: fasync::Task<()>,
618 _phantom: PhantomData<T>,
619}
620
621impl<T: RecordedVolume> ReplayState<T> {
622 fn new(handle: Box<dyn ReadObjectHandle>, volume: Arc<FxVolume>, guard: ActiveGuard) -> Self {
623 let (sender, receiver) = async_channel::unbounded::<Request<T::NodeType>>();
624
625 let mut replay_threads = Vec::with_capacity(REPLAY_THREADS);
628 for _ in 0..REPLAY_THREADS {
629 let receiver = receiver.clone();
630 let guard = guard.clone();
633 replay_threads.push(fasync::unblock(move || {
634 let _guard = guard;
635 Self::page_in_thread(receiver);
636 }));
637 }
638 let replay_threads = (Box::pin(async {
639 join_all(replay_threads).await;
640 }) as BoxFuture<'static, ()>)
641 .shared();
642
643 let scope = volume.scope().clone();
644 let cache_task = scope
645 .spawn({
646 async move {
650 let mut task = pin!(
651 async {
652 let mut local_cache: BTreeMap<
656 T::IdType,
657 Option<OpenedNode<T::NodeType>>,
658 > = BTreeMap::new();
659
660 let volume_id = volume.id();
661
662 if let Err(error) = T::new(volume)
663 .read_and_queue(handle, &sender, &mut local_cache)
664 .await
665 {
666 error!(error:?; "Failed to read back profile");
667 }
668 sender.close();
669
670 info!(
671 "Replay for volume {} opened {} of {} objects.",
672 volume_id,
673 local_cache.iter().filter(|(_, e)| e.is_some()).count(),
674 local_cache.len()
675 );
676
677 let () = std::future::pending().await;
679 }
680 .fuse()
681 );
682
683 select! {
684 _ = task => {}
685 _ = guard.on_cancel().fuse() => {}
686 }
687 }
688 })
689 .into();
690
691 Self { replay_threads, _cache_task: cache_task, _phantom: PhantomData }
692 }
693
694 fn page_in_thread(queue: async_channel::Receiver<Request<T::NodeType>>) {
695 while let Ok(request) = queue.recv_blocking() {
696 let res = request.file.vmo().op_range(
697 zx::VmoOp::PREFETCH,
698 request.offset,
699 zx::system_get_page_size() as u64,
700 );
701 if let Err(e) = res {
702 warn!("Failed to prefetch page: {:?}", e);
703 }
704 if queue.sender_count() == 0 {
706 return;
707 }
708 }
709 }
710}
711
712#[async_trait]
715pub trait ProfileState: Send + Sync {
716 fn record_new(
720 &mut self,
721 volume: &Arc<FxVolume>,
722 recording_handle: Box<dyn RecordingHandle>,
723 ) -> Box<dyn Recorder>;
724
725 fn replay_profile(
728 &mut self,
729 handle: Box<dyn ReadObjectHandle>,
730 volume: Arc<FxVolume>,
731 guard: ActiveGuard,
732 );
733
734 async fn wait_for_replay_to_finish(&mut self);
737
738 async fn wait_for_recording_to_finish(&mut self);
740}
741
742pub fn new_profile_state(is_blob: bool) -> Box<dyn ProfileState> {
743 if is_blob {
744 Box::new(ProfileStateImpl::<BlobVolume>::new())
745 } else {
746 Box::new(ProfileStateImpl::<FileVolume>::new())
747 }
748}
749
750struct ProfileStateImpl<T> {
751 recording: Option<fasync::CancelableJoinHandle<()>>,
752 replay: Option<ReplayState<T>>,
753}
754
755impl<T> ProfileStateImpl<T> {
756 fn new() -> Self {
757 Self { recording: None, replay: None }
758 }
759}
760
761#[async_trait]
762impl<T: RecordedVolume> ProfileState for ProfileStateImpl<T> {
763 fn record_new(
764 &mut self,
765 volume: &Arc<FxVolume>,
766 recording_handle: Box<dyn RecordingHandle>,
767 ) -> Box<dyn Recorder> {
768 let (sender, receiver) = async_channel::unbounded();
769 let volume = volume.clone();
770 self.recording = None;
772 let scope = volume.scope().clone();
773 self.recording = Some(
774 scope
775 .spawn(async move {
776 let recording = T::new(volume);
777 if let Err(error) = recording.record(recording_handle, receiver).await {
778 warn!(error:?; "Profile recording failed");
779 }
780 })
781 .into(),
782 );
783 Box::new(RecorderImpl::new(sender))
784 }
785
786 fn replay_profile(
787 &mut self,
788 handle: Box<dyn ReadObjectHandle>,
789 volume: Arc<FxVolume>,
790 guard: ActiveGuard,
791 ) {
792 self.replay = Some(ReplayState::new(handle, volume, guard));
793 }
794
795 async fn wait_for_replay_to_finish(&mut self) {
796 if let Some(replay) = &mut self.replay {
797 replay.replay_threads.clone().await;
798 }
799 }
800
801 async fn wait_for_recording_to_finish(&mut self) {
802 if let Some(recording) = self.recording.take() {
803 let _ = recording.await;
804 }
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::{
811 BlobMessage, BlobVolume, FileMessage, FileRecordingHandle, FileVolume, IO_SIZE, Message,
812 MutPtrByteSlice, PtrByteSlice, RecordedVolume, Request, new_profile_state,
813 };
814 use crate::fuchsia::file::FxFile;
815 use crate::fuchsia::fxblob::blob::FxBlob;
816 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
817 use crate::fuchsia::node::{FxNode, OpenedNode};
818 use crate::fuchsia::pager::PagerBacked;
819 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions, open_file_checked};
820 use crate::fuchsia::volume::FxVolume;
821 use anyhow::Error;
822 use async_trait::async_trait;
823 use delivery_blob::CompressionMode;
824 use event_listener::{Event, EventListener};
825 use fidl_fuchsia_io as fio;
826 use fuchsia_async as fasync;
827 use fuchsia_hash::Hash;
828 use fuchsia_sync::Mutex;
829 use fxfs::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
830 use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
831 use fxfs::object_store::{DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore};
832 use std::collections::BTreeMap;
833 use std::mem::size_of;
834 use std::sync::Arc;
835 use std::time::Duration;
836 use storage_device::buffer::{BufferRef, MutableBufferRef};
837 use storage_device::buffer_allocator::{BufferAllocator, BufferFuture, BufferSource};
838
839 struct FakeReaderWriterInner {
840 data: Vec<u8>,
841 delays: Vec<EventListener>,
842 }
843
844 struct FakeReaderWriter {
845 allocator: BufferAllocator,
846 inner: Arc<Mutex<FakeReaderWriterInner>>,
847 }
848
849 const BLOCK_SIZE: usize = 4096;
850
851 impl FakeReaderWriter {
852 fn new() -> Self {
853 Self {
854 allocator: BufferAllocator::new(BLOCK_SIZE, BufferSource::new(IO_SIZE * 2)),
855 inner: Arc::new(Mutex::new(FakeReaderWriterInner {
856 data: Vec::new(),
857 delays: Vec::new(),
858 })),
859 }
860 }
861
862 fn push_delay(&self, delay: EventListener) {
863 self.inner.lock().delays.insert(0, delay);
864 }
865 }
866
867 impl ObjectHandle for FakeReaderWriter {
868 fn object_id(&self) -> u64 {
869 0
870 }
871
872 fn block_size(&self) -> u64 {
873 self.allocator.block_size() as u64
874 }
875
876 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
877 self.allocator.allocate_buffer(size)
878 }
879 }
880
881 impl WriteObjectHandle for FakeReaderWriter {
882 async fn write_or_append(
883 &self,
884 offset: Option<u64>,
885 buf: BufferRef<'_>,
886 ) -> Result<u64, Error> {
887 assert!(offset.is_none());
889 let delay = self.inner.lock().delays.pop();
890 if let Some(delay) = delay {
891 delay.await;
892 }
893 buf.append_to(&mut self.inner.lock().data);
895 Ok(buf.len() as u64)
896 }
897
898 async fn truncate(&self, _size: u64) -> Result<(), Error> {
899 unreachable!();
900 }
901
902 async fn flush(&self) -> Result<(), Error> {
903 unreachable!();
904 }
905 }
906
907 async fn write_file(fixture: &TestFixture, name: &str, data: &[u8]) -> u64 {
908 let root_dir = fixture.volume().root_dir();
909 let mut transaction = fixture
910 .volume()
911 .volume()
912 .store()
913 .new_transaction(
914 lock_keys![LockKey::object(
915 fixture.volume().volume().store().store_object_id(),
916 root_dir.object_id()
917 )],
918 Options::default(),
919 )
920 .await
921 .expect("Creating transaction for new file");
922 let id = root_dir
923 .directory()
924 .create_child_file(&mut transaction, name)
925 .await
926 .expect("Creating new_file")
927 .object_id();
928 transaction.commit().await.unwrap();
929 let file = open_file_checked(
930 fixture.root(),
931 name,
932 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
933 &Default::default(),
934 )
935 .await;
936 file.write(data).await.unwrap().expect("Writing file");
937 id
938 }
939
940 #[async_trait]
941 impl ReadObjectHandle for FakeReaderWriter {
942 async fn read(&self, offset: u64, buf: MutableBufferRef<'_>) -> Result<usize, Error> {
943 let delay = self.inner.lock().delays.pop();
944 if let Some(delay) = delay {
945 delay.await;
946 }
947 let inner = self.inner.lock();
949 assert!(offset as usize <= inner.data.len());
950 let offset_end = std::cmp::min(offset as usize + buf.len(), inner.data.len());
951 let size = offset_end - offset as usize;
952 buf.subslice_mut(..size).copy_from_slice(&inner.data[offset as usize..offset_end]);
953 Ok(size)
954 }
955
956 fn get_size(&self) -> u64 {
957 self.inner.lock().data.len() as u64
958 }
959 }
960
961 #[fuchsia::test]
962 async fn test_encode_decode_blob() {
963 let mut buf = [0u8; size_of::<BlobMessage>()];
964 let m = BlobMessage { id: [88u8; 32].into(), offset: 77 };
965 m.encode_to(MutPtrByteSlice::from(&mut buf[..]));
966 let m2 = BlobMessage::decode_from(PtrByteSlice::from(&buf[..]));
967 assert_eq!(m, m2);
968 }
969
970 #[fuchsia::test]
971 async fn test_encode_decode_file() {
972 let mut buf = [0u8; size_of::<FileMessage>()];
973 let m = FileMessage { id: 88, offset: 77 };
974 m.encode_to(MutPtrByteSlice::from(&mut buf[..]));
975 let m2 = FileMessage::decode_from(PtrByteSlice::from(&buf[..]));
976 assert!(!m2.is_zeroes());
977 assert_eq!(m, m2);
978 }
979
980 const TEST_PROFILE_NAME: &str = "test_profile";
981
982 async fn get_test_profile_handle(volume: &Arc<FxVolume>) -> DataObjectHandle<FxVolume> {
983 let profile_dir = volume.get_profile_directory().await.unwrap();
984 ObjectStore::open_object(
985 volume,
986 profile_dir
987 .lookup(TEST_PROFILE_NAME)
988 .await
989 .expect("lookup failed")
990 .expect("not found")
991 .0,
992 HandleOptions::default(),
993 None,
994 )
995 .await
996 .unwrap()
997 }
998
999 async fn get_test_profile_contents(volume: &Arc<FxVolume>) -> Vec<u8> {
1000 get_test_profile_handle(volume).await.contents(1024 * 1024).await.unwrap().to_vec()
1001 }
1002
1003 #[fuchsia::test]
1004 async fn test_recording_basic_blob() {
1005 let fixture = new_blob_fixture().await;
1006 {
1007 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1008 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1009
1010 let mut state = new_profile_state(true);
1011 let volume = fixture.volume().volume();
1012
1013 {
1014 let handle =
1016 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1017 let mut recorder = state.record_new(volume, Box::new(handle));
1018 recorder.record(blob.clone(), 0).unwrap();
1019 recorder.record_open(blob).unwrap();
1020 }
1021
1022 state.wait_for_recording_to_finish().await;
1023
1024 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1025 }
1026 fixture.close().await;
1027 }
1028
1029 #[fuchsia::test]
1030 async fn test_recording_basic_file() {
1031 let fixture = TestFixture::new().await;
1032 {
1033 let id = write_file(&fixture, "foo", &[88u8]).await;
1034 let node = fixture
1035 .volume()
1036 .volume()
1037 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1038 .await
1039 .unwrap();
1040
1041 let mut state = new_profile_state(false);
1042 let volume = fixture.volume().volume();
1043
1044 {
1045 let handle =
1047 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1048 let mut recorder = state.record_new(volume, Box::new(handle));
1049 recorder.record(node.clone(), 0).unwrap();
1050 recorder.record_open(node).unwrap();
1051 }
1052 state.wait_for_recording_to_finish().await;
1053
1054 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1055 }
1056 fixture.close().await;
1057 }
1058
1059 #[fuchsia::test]
1060 async fn test_recording_filtered_without_open() {
1061 let fixture = new_blob_fixture().await;
1062 {
1063 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1064 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1065
1066 let mut state = new_profile_state(true);
1067 let volume = fixture.volume().volume();
1068
1069 {
1070 let handle =
1072 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1073 let mut recorder = state.record_new(volume, Box::new(handle));
1074 recorder.record(blob.clone(), 0).unwrap();
1075 }
1076 state.wait_for_recording_to_finish().await;
1077
1078 assert_eq!(get_test_profile_contents(volume).await.len(), 0);
1079 }
1080 fixture.close().await;
1081 }
1082
1083 #[fuchsia::test]
1084 async fn test_recording_blob_more_than_block() {
1085 let mut state = new_profile_state(true);
1086
1087 let fixture = new_blob_fixture().await;
1088 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1089 let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1090 let hash;
1091 let volume = fixture.volume().volume();
1092
1093 {
1094 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1095 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1096 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1098 let mut recorder = state.record_new(volume, Box::new(handle));
1099 recorder.record_open(blob.clone()).unwrap();
1100 for i in 0..message_count {
1101 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1102 }
1103 }
1104 state.wait_for_recording_to_finish().await;
1105
1106 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1107
1108 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1109 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1110
1111 let volume = fixture.volume().volume().clone();
1112 let task = fasync::Task::spawn(async move {
1113 let handle = Box::new(get_test_profile_handle(&volume).await);
1114 let blob = BlobVolume::new(volume);
1115 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1116 });
1117
1118 let mut recv_count = 0;
1119 while let Ok(msg) = receiver.recv().await {
1120 assert_eq!(msg.file.root(), hash);
1121 assert_eq!(msg.offset, 4096 * recv_count);
1122 recv_count += 1;
1123 }
1124 task.await;
1125 assert_eq!(recv_count, message_count as u64);
1126
1127 fixture.close().await;
1128 }
1129
1130 #[fuchsia::test]
1131 async fn test_recording_file_more_than_block() {
1132 let mut state = new_profile_state(false);
1133
1134 let fixture = TestFixture::new().await;
1135 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1136 let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1137 let id;
1138 let volume = fixture.volume().volume();
1139 {
1140 id = write_file(&fixture, "foo", &[88u8]).await;
1141 let node = volume
1142 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1143 .await
1144 .unwrap();
1145 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1147 let mut recorder = state.record_new(volume, Box::new(handle));
1148 recorder.record_open(node.clone()).unwrap();
1149 for i in 0..message_count {
1150 recorder.record(node.clone(), 4096 * i as u64).unwrap();
1151 }
1152 }
1153 state.wait_for_recording_to_finish().await;
1154
1155 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1156
1157 let mut local_cache: BTreeMap<u64, Option<OpenedNode<FxFile>>> = BTreeMap::new();
1158 let (sender, receiver) = async_channel::unbounded::<Request<FxFile>>();
1159
1160 let volume = fixture.volume().volume().clone();
1161 let task = fasync::Task::spawn(async move {
1162 let handle = Box::new(get_test_profile_handle(&volume).await);
1163 let file = FileVolume::new(volume);
1164 file.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1165 });
1166
1167 let mut recv_count = 0;
1168 while let Ok(msg) = receiver.recv().await {
1169 assert_eq!(msg.file.object_id(), id);
1170 assert_eq!(msg.offset, 4096 * recv_count);
1171 recv_count += 1;
1172 }
1173 task.await;
1174 assert_eq!(recv_count, message_count as u64);
1175
1176 fixture.close().await;
1177 }
1178
1179 #[fuchsia::test]
1180 async fn test_recording_more_than_io_size() {
1181 let fixture = new_blob_fixture().await;
1182
1183 {
1184 let mut state = new_profile_state(true);
1185 let message_count = (IO_SIZE as usize / size_of::<BlobMessage>()) + 1;
1186 let hash;
1187 let volume = fixture.volume().volume();
1188 {
1189 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1190 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1191 let handle =
1193 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1194 let mut recorder = state.record_new(volume, Box::new(handle));
1195 recorder.record_open(blob.clone()).unwrap();
1196 for i in 0..message_count {
1197 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1198 }
1199 }
1200 state.wait_for_recording_to_finish().await;
1201 assert_eq!(get_test_profile_contents(volume).await.len(), IO_SIZE + BLOCK_SIZE);
1202
1203 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1204 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1205
1206 let volume = volume.clone();
1207 let task = fasync::Task::spawn(async move {
1208 let handle = Box::new(get_test_profile_handle(&volume).await);
1209 let blob = BlobVolume::new(volume);
1210 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1211 });
1212
1213 let mut recv_count = 0;
1214 while let Ok(msg) = receiver.recv().await {
1215 assert_eq!(msg.file.root(), hash);
1216 assert_eq!(msg.offset, 4096 * recv_count);
1217 recv_count += 1;
1218 }
1219 task.await;
1220 assert_eq!(recv_count, message_count as u64);
1221 }
1222
1223 fixture.close().await;
1224 }
1225
1226 #[fuchsia::test]
1227 async fn test_replay_profile_blob() {
1228 let mut state = new_profile_state(true);
1230
1231 let mut hashes = Vec::new();
1232
1233 let fixture = new_blob_fixture().await;
1234 {
1235 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1236 let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1237
1238 let volume = fixture.volume().volume();
1239 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1240 let mut recorder = state.record_new(volume, Box::new(handle));
1241 for i in 0..message_count {
1243 let hash =
1244 fixture.write_blob(i.to_string().as_bytes(), CompressionMode::Never).await;
1245 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1246 recorder.record_open(blob.clone()).unwrap();
1247 hashes.push(hash);
1248 recorder.record(blob.clone(), 0).unwrap();
1249 }
1250 };
1251 let device = fixture.close().await;
1252 device.ensure_unique();
1253 state.wait_for_recording_to_finish().await;
1254
1255 device.reopen(false);
1256 let fixture = open_blob_fixture(device).await;
1257 {
1258 for hash in &hashes {
1261 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1262 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1263 }
1264
1265 let volume = fixture.volume().volume();
1266 state.replay_profile(
1267 Box::new(get_test_profile_handle(volume).await),
1268 volume.clone(),
1269 volume.scope().try_active_guard().unwrap(),
1270 );
1271
1272 for hash in &hashes {
1274 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1275 while blob.vmo().info().unwrap().committed_bytes == 0 {
1276 fasync::Timer::new(Duration::from_millis(25)).await;
1277 }
1278 }
1279 }
1280 fixture.close().await;
1281 }
1282
1283 #[fuchsia::test]
1284 async fn test_replay_profile_file() {
1285 let mut state = new_profile_state(false);
1287
1288 let mut ids = Vec::new();
1289
1290 let fixture = TestFixture::new().await;
1291 {
1292 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1293 let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1294
1295 let volume = fixture.volume().volume();
1296 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1297 let mut recorder = state.record_new(volume, Box::new(handle));
1298 for i in 0..message_count {
1300 let id = write_file(&fixture, &i.to_string(), &[88u8]).await;
1301 let node = fixture
1302 .volume()
1303 .volume()
1304 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1305 .await
1306 .unwrap();
1307 recorder.record_open(node.clone()).unwrap();
1308 ids.push(id);
1309 recorder.record(node.clone(), 0).unwrap();
1310 }
1311 };
1312 let device = fixture.close().await;
1313 device.ensure_unique();
1314 state.wait_for_recording_to_finish().await;
1315
1316 device.reopen(false);
1317 let fixture = TestFixture::open(
1318 device,
1319 TestFixtureOptions { encrypted: true, format: false, ..Default::default() },
1320 )
1321 .await;
1322 {
1323 for id in &ids {
1325 let file = fixture
1326 .volume()
1327 .volume()
1328 .get_or_load_node(
1329 *id,
1330 ObjectDescriptor::File,
1331 Some(fixture.volume().root_dir()),
1332 )
1333 .await
1334 .unwrap()
1335 .into_any()
1336 .downcast::<FxFile>()
1337 .unwrap();
1338 assert_eq!(file.vmo().info().unwrap().committed_bytes, 0);
1339 }
1340
1341 let volume = fixture.volume().volume();
1342 state.replay_profile(
1343 Box::new(get_test_profile_handle(volume).await),
1344 volume.clone(),
1345 volume.scope().try_active_guard().unwrap(),
1346 );
1347
1348 for id in &ids {
1350 let file = fixture
1351 .volume()
1352 .volume()
1353 .get_or_load_node(
1354 *id,
1355 ObjectDescriptor::File,
1356 Some(fixture.volume().root_dir()),
1357 )
1358 .await
1359 .unwrap()
1360 .into_any()
1361 .downcast::<FxFile>()
1362 .unwrap();
1363 while file.vmo().info().unwrap().committed_bytes == 0 {
1364 fasync::Timer::new(Duration::from_millis(25)).await;
1365 }
1366 }
1367 state.wait_for_recording_to_finish().await;
1368 }
1369 fixture.close().await;
1370 }
1371
1372 #[fuchsia::test]
1373 async fn test_recording_during_replay() {
1374 let mut state = new_profile_state(true);
1375
1376 let hash;
1377 let first_recording;
1378 let fixture = new_blob_fixture().await;
1379 let volume = fixture.volume().volume();
1380
1381 {
1383 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1384 let mut recorder = state.record_new(volume, Box::new(handle));
1385 hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1386 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1387 recorder.record_open(blob.clone()).unwrap();
1388 recorder.record(blob.clone(), 0).unwrap();
1389 }
1390
1391 state.wait_for_recording_to_finish().await;
1392 first_recording = get_test_profile_contents(volume).await;
1393 assert_ne!(first_recording.len(), 0);
1394 let device = fixture.close().await;
1395 device.ensure_unique();
1396
1397 device.reopen(false);
1398 let fixture = open_blob_fixture(device).await;
1399
1400 {
1401 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1404 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1405
1406 let volume = fixture.volume().volume();
1408 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1409 let mut recorder = state.record_new(volume, Box::new(handle));
1410 recorder.record(blob.clone(), 4096).unwrap();
1411
1412 let volume = fixture.volume().volume();
1414 state.replay_profile(
1415 Box::new(get_test_profile_handle(volume).await),
1416 volume.clone(),
1417 volume.scope().try_active_guard().unwrap(),
1418 );
1419
1420 {
1422 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1423 while blob.vmo().info().unwrap().committed_bytes == 0 {
1424 fasync::Timer::new(Duration::from_millis(25)).await;
1425 }
1426 }
1427
1428 recorder.record_open(blob.clone()).unwrap();
1431 }
1432
1433 state.wait_for_recording_to_finish().await;
1434
1435 let volume = fixture.volume().volume();
1436 let second_recording = get_test_profile_contents(volume).await;
1437 assert_ne!(second_recording.len(), 0);
1438 assert_ne!(&second_recording, &first_recording);
1439
1440 fixture.close().await;
1441 }
1442
1443 #[fuchsia::test]
1446 async fn test_replay_profile_stop_reading_early() {
1447 let mut state = new_profile_state(true);
1448 let fixture = new_blob_fixture().await;
1449
1450 {
1451 let volume = fixture.volume().volume();
1452
1453 let message;
1455 {
1456 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1457 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1458 message = BlobMessage { id: blob.root(), offset: 0 };
1459 }
1460 state.wait_for_recording_to_finish().await;
1461
1462 let replay_handle = Box::new(FakeReaderWriter::new());
1464 let mut buff = vec![0u8; IO_SIZE * 2];
1465 message.encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1466 message.encode_to_impl(
1467 (&mut buff[IO_SIZE..IO_SIZE + size_of::<BlobMessage>()]).try_into().unwrap(),
1468 );
1469
1470 replay_handle.inner.lock().data = buff;
1471 let delay1 = Event::new();
1472 replay_handle.push_delay(delay1.listen());
1473 let delay2 = Event::new();
1474 replay_handle.push_delay(delay2.listen());
1475
1476 state.replay_profile(
1477 replay_handle,
1478 volume.clone(),
1479 volume.scope().try_active_guard().unwrap(),
1480 );
1481
1482 fasync::Task::spawn(async move {
1484 fasync::Timer::new(Duration::from_millis(100)).await;
1486 delay1.notify(usize::MAX);
1487 })
1488 .detach();
1489 }
1490
1491 fixture.close().await;
1494 }
1495
1496 #[fuchsia::test]
1497 async fn test_replay_blob_missing() {
1498 let fixture = new_blob_fixture().await;
1499 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1502 let mut buff = vec![0u8; IO_SIZE];
1503 {
1504 {
1507 let message = BlobMessage { id: [42u8; 32].into(), offset: 0 };
1508 message
1509 .encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1510 }
1511
1512 {
1514 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1515 let message = BlobMessage { id: blob.root(), offset: 0 };
1516 message.encode_to_impl(
1517 (&mut buff[size_of::<BlobMessage>()..(size_of::<BlobMessage>() * 2)])
1518 .try_into()
1519 .unwrap(),
1520 );
1521 }
1522 }
1523 let device = fixture.close().await;
1524 device.ensure_unique();
1525
1526 device.reopen(false);
1527 let fixture = open_blob_fixture(device).await;
1528 {
1529 let mut state = new_profile_state(true);
1530 let volume = fixture.volume().volume();
1531
1532 let replay_handle = Box::new(FakeReaderWriter::new());
1533 replay_handle.inner.lock().data = buff;
1534
1535 state.replay_profile(
1536 replay_handle,
1537 volume.clone(),
1538 volume.scope().try_active_guard().unwrap(),
1539 );
1540
1541 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1543 while blob.vmo().info().unwrap().committed_bytes == 0 {
1544 fasync::Timer::new(Duration::from_millis(25)).await;
1545 }
1546 }
1547 fixture.close().await;
1548 }
1549
1550 #[fuchsia::test]
1551 async fn test_replay_file_missing_or_tombstoned() {
1552 let fixture = TestFixture::new().await;
1553 let mut buff = vec![0u8; IO_SIZE];
1554 let remaining_file_id;
1557 let tombstoned_file_id;
1558 {
1560 let id = write_file(&fixture, "foo", &[1, 2, 3, 4]).await;
1561 let message = FileMessage { id, offset: 0 };
1562 message.encode_to_impl((&mut buff[0..size_of::<FileMessage>()]).try_into().unwrap());
1563 }
1564 fixture
1566 .root()
1567 .unlink("foo", &fio::UnlinkOptions::default())
1568 .await
1569 .unwrap()
1570 .expect("Unlinking");
1571
1572 {
1574 tombstoned_file_id = write_file(&fixture, "bar", &[1, 2, 3, 4]).await;
1575 let message = FileMessage { id: tombstoned_file_id, offset: 0 };
1576 message.encode_to_impl(
1577 (&mut buff[size_of::<FileMessage>()..(size_of::<FileMessage>() * 2)])
1578 .try_into()
1579 .unwrap(),
1580 );
1581 }
1582
1583 {
1585 remaining_file_id = write_file(&fixture, "baz", &[1, 2, 3, 4]).await;
1586 let message = FileMessage { id: remaining_file_id, offset: 0 };
1587 message.encode_to_impl(
1588 (&mut buff[(size_of::<FileMessage>() * 2)..(size_of::<FileMessage>() * 3)])
1589 .try_into()
1590 .unwrap(),
1591 );
1592 }
1593 let device = fixture.close().await;
1594 device.ensure_unique();
1595
1596 device.reopen(false);
1597 let fixture =
1598 TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
1599 .await;
1600 {
1601 let tombstoned_file = fixture
1604 .volume()
1605 .volume()
1606 .get_or_load_node(tombstoned_file_id, ObjectDescriptor::File, None)
1607 .await
1608 .expect("Opening file object")
1609 .into_any()
1610 .downcast::<FxFile>()
1611 .unwrap();
1612 fixture
1613 .root()
1614 .unlink("bar", &fio::UnlinkOptions::default())
1615 .await
1616 .unwrap()
1617 .expect("Unlinking");
1618
1619 let mut state = new_profile_state(false);
1620 let volume = fixture.volume().volume();
1621
1622 let replay_handle = Box::new(FakeReaderWriter::new());
1623 replay_handle.inner.lock().data = buff;
1624
1625 state.replay_profile(
1626 replay_handle,
1627 volume.clone(),
1628 volume.scope().try_active_guard().unwrap(),
1629 );
1630
1631 let remaining_file = fixture
1633 .volume()
1634 .volume()
1635 .get_or_load_node(remaining_file_id, ObjectDescriptor::File, None)
1636 .await
1637 .expect("Opening file object")
1638 .into_any()
1639 .downcast::<FxFile>()
1640 .unwrap();
1641 while remaining_file.vmo().info().unwrap().committed_bytes == 0 {
1642 fasync::Timer::new(Duration::from_millis(25)).await;
1643 }
1644
1645 assert_eq!(tombstoned_file.vmo().info().unwrap().committed_bytes, 0);
1648 }
1649 fixture.close().await;
1650 }
1651
1652 #[fuchsia::test]
1653 async fn test_recording_stopped_by_scope_shutdown() {
1654 let fixture = TestFixture::new().await;
1655 {
1656 let volumes_directory = fixture.volumes_directory();
1657 let mut state = new_profile_state(false);
1658 let (store_id, _recorder) = {
1661 let volume_and_root = volumes_directory
1662 .create_and_mount_volume("other_volume", None, false, None)
1663 .await
1664 .unwrap();
1665 let volume = volume_and_root.into_volume();
1666
1667 let handle =
1668 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1669 (volume.store().store_object_id(), state.record_new(&volume, Box::new(handle)))
1670 };
1671 volumes_directory.lock().await.unmount(store_id).await.expect("Unmounting");
1672 state.wait_for_recording_to_finish().await;
1673
1674 let volume_and_root =
1675 volumes_directory.mount_volume("other_volume", None, false).await.unwrap();
1676 let volume = volume_and_root.into_volume();
1677 let profile_dir = volume.get_profile_directory().await.unwrap();
1678 assert!(profile_dir.lookup(TEST_PROFILE_NAME).await.unwrap().is_none());
1679 }
1680 fixture.close().await;
1681 }
1682}