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