1use std::io::Read;
9use uuid::Uuid;
10
11#[derive(Debug, thiserror::Error)]
13pub enum StreamError {
14 #[error("invalid send stream: bad magic")]
16 BadMagic,
17 #[error("unsupported send stream version {0} (supported: 1-3)")]
19 UnsupportedVersion(u32),
20 #[error(
22 "CRC mismatch for command {cmd}: expected {expected:#010x}, got {computed:#010x}"
23 )]
24 CrcMismatch {
25 cmd: u16,
26 expected: u32,
27 computed: u32,
28 },
29 #[error("truncated send stream header: {0}")]
31 TruncatedHeader(std::io::Error),
32 #[error("truncated send stream payload: {0}")]
34 TruncatedPayload(std::io::Error),
35 #[error("truncated TLV: {detail}")]
37 TruncatedTlv { detail: String },
38 #[error("invalid TLV attribute type {0}")]
40 InvalidTlvType(u16),
41 #[error("missing required attribute: {0}")]
43 MissingAttribute(&'static str),
44 #[error("attribute {name}: {detail}")]
46 InvalidAttribute { name: &'static str, detail: String },
47 #[error("unknown send stream command type {0}")]
49 UnknownCommand(u16),
50 #[error(transparent)]
52 Io(#[from] std::io::Error),
53}
54
55type Result<T> = std::result::Result<T, StreamError>;
56
57const SEND_STREAM_MAGIC: &[u8] = b"btrfs-stream\0";
58const SEND_STREAM_MAGIC_LEN: usize = 13;
59const STREAM_HEADER_LEN: usize = SEND_STREAM_MAGIC_LEN + 4; const CMD_HEADER_LEN: usize = 10; const MAX_ATTRS: usize = 36;
66
67const BTRFS_SEND_C_SUBVOL: u16 = 1;
69const BTRFS_SEND_C_SNAPSHOT: u16 = 2;
70const BTRFS_SEND_C_MKFILE: u16 = 3;
71const BTRFS_SEND_C_MKDIR: u16 = 4;
72const BTRFS_SEND_C_MKNOD: u16 = 5;
73const BTRFS_SEND_C_MKFIFO: u16 = 6;
74const BTRFS_SEND_C_MKSOCK: u16 = 7;
75const BTRFS_SEND_C_SYMLINK: u16 = 8;
76const BTRFS_SEND_C_RENAME: u16 = 9;
77const BTRFS_SEND_C_LINK: u16 = 10;
78const BTRFS_SEND_C_UNLINK: u16 = 11;
79const BTRFS_SEND_C_RMDIR: u16 = 12;
80const BTRFS_SEND_C_SET_XATTR: u16 = 13;
81const BTRFS_SEND_C_REMOVE_XATTR: u16 = 14;
82const BTRFS_SEND_C_WRITE: u16 = 15;
83const BTRFS_SEND_C_CLONE: u16 = 16;
84const BTRFS_SEND_C_TRUNCATE: u16 = 17;
85const BTRFS_SEND_C_CHMOD: u16 = 18;
86const BTRFS_SEND_C_CHOWN: u16 = 19;
87const BTRFS_SEND_C_UTIMES: u16 = 20;
88const BTRFS_SEND_C_END: u16 = 21;
89const BTRFS_SEND_C_UPDATE_EXTENT: u16 = 22;
90const BTRFS_SEND_C_FALLOCATE: u16 = 23;
92const BTRFS_SEND_C_FILEATTR: u16 = 24;
93const BTRFS_SEND_C_ENCODED_WRITE: u16 = 25;
94const BTRFS_SEND_C_ENABLE_VERITY: u16 = 26;
96
97const BTRFS_SEND_A_UUID: u16 = 1;
99const BTRFS_SEND_A_CTRANSID: u16 = 2;
100#[allow(dead_code)]
101const BTRFS_SEND_A_INO: u16 = 3;
102const BTRFS_SEND_A_SIZE: u16 = 4;
103const BTRFS_SEND_A_MODE: u16 = 5;
104const BTRFS_SEND_A_UID: u16 = 6;
105const BTRFS_SEND_A_GID: u16 = 7;
106const BTRFS_SEND_A_RDEV: u16 = 8;
107const BTRFS_SEND_A_CTIME: u16 = 9;
108const BTRFS_SEND_A_MTIME: u16 = 10;
109const BTRFS_SEND_A_ATIME: u16 = 11;
110#[allow(dead_code)]
111const BTRFS_SEND_A_OTIME: u16 = 12;
112const BTRFS_SEND_A_XATTR_NAME: u16 = 13;
113const BTRFS_SEND_A_XATTR_DATA: u16 = 14;
114const BTRFS_SEND_A_PATH: u16 = 15;
115const BTRFS_SEND_A_PATH_TO: u16 = 16;
116const BTRFS_SEND_A_PATH_LINK: u16 = 17;
117const BTRFS_SEND_A_FILE_OFFSET: u16 = 18;
118const BTRFS_SEND_A_DATA: u16 = 19;
119const BTRFS_SEND_A_CLONE_UUID: u16 = 20;
120const BTRFS_SEND_A_CLONE_CTRANSID: u16 = 21;
121const BTRFS_SEND_A_CLONE_PATH: u16 = 22;
122const BTRFS_SEND_A_CLONE_OFFSET: u16 = 23;
123const BTRFS_SEND_A_CLONE_LEN: u16 = 24;
124const BTRFS_SEND_A_FALLOCATE_MODE: u16 = 25;
126const BTRFS_SEND_A_FILEATTR: u16 = 26;
127const BTRFS_SEND_A_UNENCODED_FILE_LEN: u16 = 27;
128const BTRFS_SEND_A_UNENCODED_LEN: u16 = 28;
129const BTRFS_SEND_A_UNENCODED_OFFSET: u16 = 29;
130const BTRFS_SEND_A_COMPRESSION: u16 = 30;
131const BTRFS_SEND_A_ENCRYPTION: u16 = 31;
132const BTRFS_SEND_A_VERITY_ALGORITHM: u16 = 32;
134const BTRFS_SEND_A_VERITY_BLOCK_SIZE: u16 = 33;
135const BTRFS_SEND_A_VERITY_SALT_DATA: u16 = 34;
136const BTRFS_SEND_A_VERITY_SIG_DATA: u16 = 35;
137
138#[derive(Debug, Clone, Copy)]
140pub struct Timespec {
141 pub sec: u64,
142 pub nsec: u32,
143}
144
145#[derive(Debug)]
147pub enum StreamCommand {
148 Subvol {
149 path: String,
150 uuid: Uuid,
151 ctransid: u64,
152 },
153 Snapshot {
154 path: String,
155 uuid: Uuid,
156 ctransid: u64,
157 clone_uuid: Uuid,
158 clone_ctransid: u64,
159 },
160 Mkfile {
161 path: String,
162 },
163 Mkdir {
164 path: String,
165 },
166 Mknod {
167 path: String,
168 mode: u64,
169 rdev: u64,
170 },
171 Mkfifo {
172 path: String,
173 },
174 Mksock {
175 path: String,
176 },
177 Symlink {
178 path: String,
179 target: String,
180 },
181 Rename {
182 from: String,
183 to: String,
184 },
185 Link {
186 path: String,
187 target: String,
188 },
189 Unlink {
190 path: String,
191 },
192 Rmdir {
193 path: String,
194 },
195 Write {
196 path: String,
197 offset: u64,
198 data: Vec<u8>,
199 },
200 Clone {
201 path: String,
202 offset: u64,
203 len: u64,
204 clone_uuid: Uuid,
205 clone_ctransid: u64,
206 clone_path: String,
207 clone_offset: u64,
208 },
209 SetXattr {
210 path: String,
211 name: String,
212 data: Vec<u8>,
213 },
214 RemoveXattr {
215 path: String,
216 name: String,
217 },
218 Truncate {
219 path: String,
220 size: u64,
221 },
222 Chmod {
223 path: String,
224 mode: u64,
225 },
226 Chown {
227 path: String,
228 uid: u64,
229 gid: u64,
230 },
231 Utimes {
232 path: String,
233 atime: Timespec,
234 mtime: Timespec,
235 ctime: Timespec,
236 },
237 UpdateExtent {
238 path: String,
239 offset: u64,
240 len: u64,
241 },
242 EncodedWrite {
245 path: String,
246 offset: u64,
247 unencoded_file_len: u64,
249 unencoded_len: u64,
251 unencoded_offset: u64,
253 compression: u32,
255 encryption: u32,
257 data: Vec<u8>,
258 },
259 Fallocate {
261 path: String,
262 mode: u32,
264 offset: u64,
265 len: u64,
266 },
267 Fileattr {
269 path: String,
270 attr: u64,
271 },
272 EnableVerity {
274 path: String,
275 algorithm: u8,
276 block_size: u32,
277 salt: Vec<u8>,
278 sig: Vec<u8>,
279 },
280 End,
281}
282
283#[derive(Debug)]
285pub struct StreamReader<R: Read> {
286 reader: R,
287 version: u32,
288 buf: Vec<u8>,
289}
290
291impl<R: Read> StreamReader<R> {
292 pub fn new(mut reader: R) -> Result<Self> {
304 let mut header = [0u8; STREAM_HEADER_LEN];
305 reader
306 .read_exact(&mut header)
307 .map_err(StreamError::TruncatedHeader)?;
308
309 if &header[..SEND_STREAM_MAGIC_LEN] != SEND_STREAM_MAGIC {
310 return Err(StreamError::BadMagic);
311 }
312
313 let version = u32::from_le_bytes(
314 header[SEND_STREAM_MAGIC_LEN..STREAM_HEADER_LEN]
315 .try_into()
316 .unwrap(),
317 );
318
319 if version == 0 || version > 3 {
320 return Err(StreamError::UnsupportedVersion(version));
321 }
322
323 Ok(Self {
324 reader,
325 version,
326 buf: Vec::with_capacity(64 * 1024),
327 })
328 }
329
330 #[must_use]
332 pub fn version(&self) -> u32 {
333 self.version
334 }
335
336 #[must_use]
338 pub fn into_inner(self) -> R {
339 self.reader
340 }
341
342 #[allow(clippy::too_many_lines)]
357 pub fn next_command(&mut self) -> Result<Option<StreamCommand>> {
358 let mut cmd_hdr = [0u8; CMD_HEADER_LEN];
360 if !read_exact_or_eof(&mut self.reader, &mut cmd_hdr)? {
361 return Ok(None); }
363
364 #[allow(clippy::cast_possible_truncation)]
365 let payload_len =
367 u32::from_le_bytes(cmd_hdr[0..4].try_into().unwrap()) as usize;
368 let cmd = u16::from_le_bytes(cmd_hdr[4..6].try_into().unwrap());
369 let expected_crc =
370 u32::from_le_bytes(cmd_hdr[6..10].try_into().unwrap());
371
372 self.buf.resize(payload_len, 0);
374 self.reader
375 .read_exact(&mut self.buf)
376 .map_err(StreamError::TruncatedPayload)?;
377
378 let crc = crc32c::crc32c_append(!0, &cmd_hdr[0..6]); let crc = crc32c::crc32c_append(crc, &[0u8; 4]); let crc = crc32c::crc32c_append(crc, &self.buf); let computed_crc = !crc;
387 if computed_crc != expected_crc {
388 return Err(StreamError::CrcMismatch {
389 cmd,
390 expected: expected_crc,
391 computed: computed_crc,
392 });
393 }
394
395 let attrs = parse_tlv_attrs(&self.buf, self.version)?;
397
398 match cmd {
400 BTRFS_SEND_C_SUBVOL => Ok(Some(StreamCommand::Subvol {
401 path: attr_string(
402 &self.buf,
403 &attrs,
404 BTRFS_SEND_A_PATH,
405 "path",
406 )?,
407 uuid: attr_uuid(&self.buf, &attrs, BTRFS_SEND_A_UUID, "uuid")?,
408 ctransid: attr_u64(
409 &self.buf,
410 &attrs,
411 BTRFS_SEND_A_CTRANSID,
412 "ctransid",
413 )?,
414 })),
415 BTRFS_SEND_C_SNAPSHOT => Ok(Some(StreamCommand::Snapshot {
416 path: attr_string(
417 &self.buf,
418 &attrs,
419 BTRFS_SEND_A_PATH,
420 "path",
421 )?,
422 uuid: attr_uuid(&self.buf, &attrs, BTRFS_SEND_A_UUID, "uuid")?,
423 ctransid: attr_u64(
424 &self.buf,
425 &attrs,
426 BTRFS_SEND_A_CTRANSID,
427 "ctransid",
428 )?,
429 clone_uuid: attr_uuid(
430 &self.buf,
431 &attrs,
432 BTRFS_SEND_A_CLONE_UUID,
433 "clone_uuid",
434 )?,
435 clone_ctransid: attr_u64(
436 &self.buf,
437 &attrs,
438 BTRFS_SEND_A_CLONE_CTRANSID,
439 "clone_ctransid",
440 )?,
441 })),
442 BTRFS_SEND_C_MKFILE => Ok(Some(StreamCommand::Mkfile {
443 path: attr_string(
444 &self.buf,
445 &attrs,
446 BTRFS_SEND_A_PATH,
447 "path",
448 )?,
449 })),
450 BTRFS_SEND_C_MKDIR => Ok(Some(StreamCommand::Mkdir {
451 path: attr_string(
452 &self.buf,
453 &attrs,
454 BTRFS_SEND_A_PATH,
455 "path",
456 )?,
457 })),
458 BTRFS_SEND_C_MKNOD => Ok(Some(StreamCommand::Mknod {
459 path: attr_string(
460 &self.buf,
461 &attrs,
462 BTRFS_SEND_A_PATH,
463 "path",
464 )?,
465 mode: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_MODE, "mode")?,
466 rdev: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_RDEV, "rdev")?,
467 })),
468 BTRFS_SEND_C_MKFIFO => Ok(Some(StreamCommand::Mkfifo {
469 path: attr_string(
470 &self.buf,
471 &attrs,
472 BTRFS_SEND_A_PATH,
473 "path",
474 )?,
475 })),
476 BTRFS_SEND_C_MKSOCK => Ok(Some(StreamCommand::Mksock {
477 path: attr_string(
478 &self.buf,
479 &attrs,
480 BTRFS_SEND_A_PATH,
481 "path",
482 )?,
483 })),
484 BTRFS_SEND_C_SYMLINK => Ok(Some(StreamCommand::Symlink {
485 path: attr_string(
486 &self.buf,
487 &attrs,
488 BTRFS_SEND_A_PATH,
489 "path",
490 )?,
491 target: attr_string(
492 &self.buf,
493 &attrs,
494 BTRFS_SEND_A_PATH_LINK,
495 "link_target",
496 )?,
497 })),
498 BTRFS_SEND_C_RENAME => Ok(Some(StreamCommand::Rename {
499 from: attr_string(
500 &self.buf,
501 &attrs,
502 BTRFS_SEND_A_PATH,
503 "path",
504 )?,
505 to: attr_string(
506 &self.buf,
507 &attrs,
508 BTRFS_SEND_A_PATH_TO,
509 "path_to",
510 )?,
511 })),
512 BTRFS_SEND_C_LINK => Ok(Some(StreamCommand::Link {
513 path: attr_string(
514 &self.buf,
515 &attrs,
516 BTRFS_SEND_A_PATH,
517 "path",
518 )?,
519 target: attr_string(
520 &self.buf,
521 &attrs,
522 BTRFS_SEND_A_PATH_LINK,
523 "link_target",
524 )?,
525 })),
526 BTRFS_SEND_C_UNLINK => Ok(Some(StreamCommand::Unlink {
527 path: attr_string(
528 &self.buf,
529 &attrs,
530 BTRFS_SEND_A_PATH,
531 "path",
532 )?,
533 })),
534 BTRFS_SEND_C_RMDIR => Ok(Some(StreamCommand::Rmdir {
535 path: attr_string(
536 &self.buf,
537 &attrs,
538 BTRFS_SEND_A_PATH,
539 "path",
540 )?,
541 })),
542 BTRFS_SEND_C_SET_XATTR => Ok(Some(StreamCommand::SetXattr {
543 path: attr_string(
544 &self.buf,
545 &attrs,
546 BTRFS_SEND_A_PATH,
547 "path",
548 )?,
549 name: attr_string(
550 &self.buf,
551 &attrs,
552 BTRFS_SEND_A_XATTR_NAME,
553 "xattr_name",
554 )?,
555 data: attr_data(
556 &self.buf,
557 &attrs,
558 BTRFS_SEND_A_XATTR_DATA,
559 "xattr_data",
560 )?,
561 })),
562 BTRFS_SEND_C_REMOVE_XATTR => Ok(Some(StreamCommand::RemoveXattr {
563 path: attr_string(
564 &self.buf,
565 &attrs,
566 BTRFS_SEND_A_PATH,
567 "path",
568 )?,
569 name: attr_string(
570 &self.buf,
571 &attrs,
572 BTRFS_SEND_A_XATTR_NAME,
573 "xattr_name",
574 )?,
575 })),
576 BTRFS_SEND_C_WRITE => Ok(Some(StreamCommand::Write {
577 path: attr_string(
578 &self.buf,
579 &attrs,
580 BTRFS_SEND_A_PATH,
581 "path",
582 )?,
583 offset: attr_u64(
584 &self.buf,
585 &attrs,
586 BTRFS_SEND_A_FILE_OFFSET,
587 "file_offset",
588 )?,
589 data: attr_data(&self.buf, &attrs, BTRFS_SEND_A_DATA, "data")?,
590 })),
591 BTRFS_SEND_C_CLONE => Ok(Some(StreamCommand::Clone {
592 path: attr_string(
593 &self.buf,
594 &attrs,
595 BTRFS_SEND_A_PATH,
596 "path",
597 )?,
598 offset: attr_u64(
599 &self.buf,
600 &attrs,
601 BTRFS_SEND_A_FILE_OFFSET,
602 "file_offset",
603 )?,
604 len: attr_u64(
605 &self.buf,
606 &attrs,
607 BTRFS_SEND_A_CLONE_LEN,
608 "clone_len",
609 )?,
610 clone_uuid: attr_uuid(
611 &self.buf,
612 &attrs,
613 BTRFS_SEND_A_CLONE_UUID,
614 "clone_uuid",
615 )?,
616 clone_ctransid: attr_u64(
617 &self.buf,
618 &attrs,
619 BTRFS_SEND_A_CLONE_CTRANSID,
620 "clone_ctransid",
621 )?,
622 clone_path: attr_string(
623 &self.buf,
624 &attrs,
625 BTRFS_SEND_A_CLONE_PATH,
626 "clone_path",
627 )?,
628 clone_offset: attr_u64(
629 &self.buf,
630 &attrs,
631 BTRFS_SEND_A_CLONE_OFFSET,
632 "clone_offset",
633 )?,
634 })),
635 BTRFS_SEND_C_TRUNCATE => Ok(Some(StreamCommand::Truncate {
636 path: attr_string(
637 &self.buf,
638 &attrs,
639 BTRFS_SEND_A_PATH,
640 "path",
641 )?,
642 size: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_SIZE, "size")?,
643 })),
644 BTRFS_SEND_C_CHMOD => Ok(Some(StreamCommand::Chmod {
645 path: attr_string(
646 &self.buf,
647 &attrs,
648 BTRFS_SEND_A_PATH,
649 "path",
650 )?,
651 mode: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_MODE, "mode")?,
652 })),
653 BTRFS_SEND_C_CHOWN => Ok(Some(StreamCommand::Chown {
654 path: attr_string(
655 &self.buf,
656 &attrs,
657 BTRFS_SEND_A_PATH,
658 "path",
659 )?,
660 uid: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_UID, "uid")?,
661 gid: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_GID, "gid")?,
662 })),
663 BTRFS_SEND_C_UTIMES => Ok(Some(StreamCommand::Utimes {
664 path: attr_string(
665 &self.buf,
666 &attrs,
667 BTRFS_SEND_A_PATH,
668 "path",
669 )?,
670 atime: attr_timespec(
671 &self.buf,
672 &attrs,
673 BTRFS_SEND_A_ATIME,
674 "atime",
675 )?,
676 mtime: attr_timespec(
677 &self.buf,
678 &attrs,
679 BTRFS_SEND_A_MTIME,
680 "mtime",
681 )?,
682 ctime: attr_timespec(
683 &self.buf,
684 &attrs,
685 BTRFS_SEND_A_CTIME,
686 "ctime",
687 )?,
688 })),
689 BTRFS_SEND_C_UPDATE_EXTENT => {
690 Ok(Some(StreamCommand::UpdateExtent {
691 path: attr_string(
692 &self.buf,
693 &attrs,
694 BTRFS_SEND_A_PATH,
695 "path",
696 )?,
697 offset: attr_u64(
698 &self.buf,
699 &attrs,
700 BTRFS_SEND_A_FILE_OFFSET,
701 "file_offset",
702 )?,
703 len: attr_u64(
704 &self.buf,
705 &attrs,
706 BTRFS_SEND_A_SIZE,
707 "size",
708 )?,
709 }))
710 }
711 BTRFS_SEND_C_FALLOCATE => Ok(Some(StreamCommand::Fallocate {
712 path: attr_string(
713 &self.buf,
714 &attrs,
715 BTRFS_SEND_A_PATH,
716 "path",
717 )?,
718 mode: attr_u32(
719 &self.buf,
720 &attrs,
721 BTRFS_SEND_A_FALLOCATE_MODE,
722 "fallocate_mode",
723 )?,
724 offset: attr_u64(
725 &self.buf,
726 &attrs,
727 BTRFS_SEND_A_FILE_OFFSET,
728 "file_offset",
729 )?,
730 len: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_SIZE, "size")?,
731 })),
732 BTRFS_SEND_C_FILEATTR => Ok(Some(StreamCommand::Fileattr {
733 path: attr_string(
734 &self.buf,
735 &attrs,
736 BTRFS_SEND_A_PATH,
737 "path",
738 )?,
739 attr: attr_u64(
740 &self.buf,
741 &attrs,
742 BTRFS_SEND_A_FILEATTR,
743 "fileattr",
744 )?,
745 })),
746 BTRFS_SEND_C_ENCODED_WRITE => {
747 Ok(Some(StreamCommand::EncodedWrite {
748 path: attr_string(
749 &self.buf,
750 &attrs,
751 BTRFS_SEND_A_PATH,
752 "path",
753 )?,
754 offset: attr_u64(
755 &self.buf,
756 &attrs,
757 BTRFS_SEND_A_FILE_OFFSET,
758 "file_offset",
759 )?,
760 unencoded_file_len: attr_u64(
761 &self.buf,
762 &attrs,
763 BTRFS_SEND_A_UNENCODED_FILE_LEN,
764 "unencoded_file_len",
765 )?,
766 unencoded_len: attr_u64(
767 &self.buf,
768 &attrs,
769 BTRFS_SEND_A_UNENCODED_LEN,
770 "unencoded_len",
771 )?,
772 unencoded_offset: attr_u64(
773 &self.buf,
774 &attrs,
775 BTRFS_SEND_A_UNENCODED_OFFSET,
776 "unencoded_offset",
777 )?,
778 compression: attr_opt_u32(
780 &self.buf,
781 &attrs,
782 BTRFS_SEND_A_COMPRESSION,
783 0,
784 ),
785 encryption: attr_opt_u32(
786 &self.buf,
787 &attrs,
788 BTRFS_SEND_A_ENCRYPTION,
789 0,
790 ),
791 data: attr_data(
792 &self.buf,
793 &attrs,
794 BTRFS_SEND_A_DATA,
795 "data",
796 )?,
797 }))
798 }
799 BTRFS_SEND_C_ENABLE_VERITY => {
800 Ok(Some(StreamCommand::EnableVerity {
801 path: attr_string(
802 &self.buf,
803 &attrs,
804 BTRFS_SEND_A_PATH,
805 "path",
806 )?,
807 algorithm: attr_u8(
808 &self.buf,
809 &attrs,
810 BTRFS_SEND_A_VERITY_ALGORITHM,
811 "verity_algorithm",
812 )?,
813 block_size: attr_u32(
814 &self.buf,
815 &attrs,
816 BTRFS_SEND_A_VERITY_BLOCK_SIZE,
817 "verity_block_size",
818 )?,
819 salt: attr_data(
820 &self.buf,
821 &attrs,
822 BTRFS_SEND_A_VERITY_SALT_DATA,
823 "verity_salt",
824 )?,
825 sig: attr_data(
826 &self.buf,
827 &attrs,
828 BTRFS_SEND_A_VERITY_SIG_DATA,
829 "verity_sig",
830 )?,
831 }))
832 }
833 BTRFS_SEND_C_END => Ok(Some(StreamCommand::End)),
834 _ => Err(StreamError::UnknownCommand(cmd)),
835 }
836 }
837}
838
839fn read_exact_or_eof(reader: &mut impl Read, buf: &mut [u8]) -> Result<bool> {
841 let mut pos = 0;
842 while pos < buf.len() {
843 match reader.read(&mut buf[pos..]) {
844 Ok(0) => {
845 if pos == 0 {
846 return Ok(false);
847 }
848 return Err(StreamError::TruncatedPayload(
849 std::io::Error::new(
850 std::io::ErrorKind::UnexpectedEof,
851 format!("unexpected EOF after {pos} bytes"),
852 ),
853 ));
854 }
855 Ok(n) => pos += n,
856 Err(e) => return Err(StreamError::TruncatedPayload(e)),
857 }
858 }
859 Ok(true)
860}
861
862type AttrTable = [Option<(usize, usize)>; MAX_ATTRS];
865
866fn parse_tlv_attrs(payload: &[u8], version: u32) -> Result<AttrTable> {
868 let mut attrs: AttrTable = [None; MAX_ATTRS];
869 let mut pos = 0;
870
871 while pos < payload.len() {
872 if pos + 2 > payload.len() {
873 return Err(StreamError::TruncatedTlv {
874 detail: "not enough bytes for type field".into(),
875 });
876 }
877 let tlv_type =
878 u16::from_le_bytes(payload[pos..pos + 2].try_into().unwrap());
879 pos += 2;
880
881 if tlv_type == 0 || usize::from(tlv_type) > MAX_ATTRS {
882 return Err(StreamError::InvalidTlvType(tlv_type));
883 }
884
885 let tlv_len = if version >= 2 && tlv_type == BTRFS_SEND_A_DATA {
888 payload.len() - pos
889 } else {
890 if pos + 2 > payload.len() {
891 return Err(StreamError::TruncatedTlv {
892 detail: "not enough bytes for length field".into(),
893 });
894 }
895 let len = usize::from(u16::from_le_bytes(
896 payload[pos..pos + 2].try_into().unwrap(),
897 ));
898 pos += 2;
899 len
900 };
901
902 if pos + tlv_len > payload.len() {
903 return Err(StreamError::TruncatedTlv {
904 detail: format!(
905 "attribute type {tlv_type} needs {tlv_len} bytes but only {} remain",
906 payload.len() - pos
907 ),
908 });
909 }
910
911 attrs[usize::from(tlv_type - 1)] = Some((pos, tlv_len));
912 pos += tlv_len;
913 }
914
915 Ok(attrs)
916}
917
918fn get_attr<'a>(
919 buf: &'a [u8],
920 attrs: &AttrTable,
921 attr_type: u16,
922 name: &'static str,
923) -> Result<&'a [u8]> {
924 let (offset, len) = attrs[usize::from(attr_type - 1)]
925 .ok_or(StreamError::MissingAttribute(name))?;
926 Ok(&buf[offset..offset + len])
927}
928
929fn attr_u64(
930 buf: &[u8],
931 attrs: &AttrTable,
932 attr_type: u16,
933 name: &'static str,
934) -> Result<u64> {
935 let data = get_attr(buf, attrs, attr_type, name)?;
936 if data.len() < 8 {
937 return Err(StreamError::InvalidAttribute {
938 name,
939 detail: format!("too short for u64: {} bytes", data.len()),
940 });
941 }
942 Ok(u64::from_le_bytes(data[0..8].try_into().unwrap()))
943}
944
945fn attr_string(
946 buf: &[u8],
947 attrs: &AttrTable,
948 attr_type: u16,
949 name: &'static str,
950) -> Result<String> {
951 let data = get_attr(buf, attrs, attr_type, name)?;
952 let s = if data.last() == Some(&0) {
954 &data[..data.len() - 1]
955 } else {
956 data
957 };
958 String::from_utf8(s.to_vec()).map_err(|_| StreamError::InvalidAttribute {
959 name,
960 detail: "not valid UTF-8".into(),
961 })
962}
963
964fn attr_uuid(
965 buf: &[u8],
966 attrs: &AttrTable,
967 attr_type: u16,
968 name: &'static str,
969) -> Result<Uuid> {
970 let data = get_attr(buf, attrs, attr_type, name)?;
971 if data.len() < 16 {
972 return Err(StreamError::InvalidAttribute {
973 name,
974 detail: format!("too short for UUID: {} bytes", data.len()),
975 });
976 }
977 Ok(Uuid::from_bytes(data[0..16].try_into().unwrap()))
978}
979
980fn attr_timespec(
981 buf: &[u8],
982 attrs: &AttrTable,
983 attr_type: u16,
984 name: &'static str,
985) -> Result<Timespec> {
986 let data = get_attr(buf, attrs, attr_type, name)?;
987 if data.len() < 12 {
988 return Err(StreamError::InvalidAttribute {
989 name,
990 detail: format!("too short for timespec: {} bytes", data.len()),
991 });
992 }
993 Ok(Timespec {
994 sec: u64::from_le_bytes(data[0..8].try_into().unwrap()),
995 nsec: u32::from_le_bytes(data[8..12].try_into().unwrap()),
996 })
997}
998
999fn attr_u32(
1000 buf: &[u8],
1001 attrs: &AttrTable,
1002 attr_type: u16,
1003 name: &'static str,
1004) -> Result<u32> {
1005 let data = get_attr(buf, attrs, attr_type, name)?;
1006 if data.len() < 4 {
1007 return Err(StreamError::InvalidAttribute {
1008 name,
1009 detail: format!("too short for u32: {} bytes", data.len()),
1010 });
1011 }
1012 Ok(u32::from_le_bytes(data[0..4].try_into().unwrap()))
1013}
1014
1015fn attr_u8(
1016 buf: &[u8],
1017 attrs: &AttrTable,
1018 attr_type: u16,
1019 name: &'static str,
1020) -> Result<u8> {
1021 let data = get_attr(buf, attrs, attr_type, name)?;
1022 if data.is_empty() {
1023 return Err(StreamError::InvalidAttribute {
1024 name,
1025 detail: "is empty".into(),
1026 });
1027 }
1028 Ok(data[0])
1029}
1030
1031fn get_attr_opt<'a>(
1034 buf: &'a [u8],
1035 attrs: &AttrTable,
1036 attr_type: u16,
1037) -> Option<&'a [u8]> {
1038 let (offset, len) = attrs[usize::from(attr_type - 1)]?;
1039 Some(&buf[offset..offset + len])
1040}
1041
1042fn attr_opt_u32(
1043 buf: &[u8],
1044 attrs: &AttrTable,
1045 attr_type: u16,
1046 default: u32,
1047) -> u32 {
1048 match get_attr_opt(buf, attrs, attr_type) {
1049 Some(data) if data.len() >= 4 => {
1050 u32::from_le_bytes(data[0..4].try_into().unwrap())
1051 }
1052 _ => default,
1053 }
1054}
1055
1056fn attr_data(
1057 buf: &[u8],
1058 attrs: &AttrTable,
1059 attr_type: u16,
1060 name: &'static str,
1061) -> Result<Vec<u8>> {
1062 let data = get_attr(buf, attrs, attr_type, name)?;
1063 Ok(data.to_vec())
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use super::*;
1069 use std::io::Cursor;
1070
1071 fn v1_header() -> Vec<u8> {
1073 let mut buf = Vec::new();
1074 buf.extend_from_slice(SEND_STREAM_MAGIC);
1075 buf.extend_from_slice(&1u32.to_le_bytes());
1076 buf
1077 }
1078
1079 fn build_command(cmd: u16, payload: &[u8]) -> Vec<u8> {
1082 let len = payload.len() as u32;
1083 let mut crc_input = Vec::new();
1086 crc_input.extend_from_slice(&len.to_le_bytes());
1087 crc_input.extend_from_slice(&cmd.to_le_bytes());
1088 crc_input.extend_from_slice(&[0u8; 4]); crc_input.extend_from_slice(payload);
1090 let crc = !crc32c::crc32c_append(!0, &crc_input);
1091
1092 let mut out = Vec::new();
1093 out.extend_from_slice(&len.to_le_bytes());
1094 out.extend_from_slice(&cmd.to_le_bytes());
1095 out.extend_from_slice(&crc.to_le_bytes());
1096 out.extend_from_slice(payload);
1097 out
1098 }
1099
1100 fn tlv(attr_type: u16, data: &[u8]) -> Vec<u8> {
1102 let mut buf = Vec::new();
1103 buf.extend_from_slice(&attr_type.to_le_bytes());
1104 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
1105 buf.extend_from_slice(data);
1106 buf
1107 }
1108
1109 fn tlv_string(attr_type: u16, s: &str) -> Vec<u8> {
1111 let mut data = s.as_bytes().to_vec();
1112 data.push(0); tlv(attr_type, &data)
1114 }
1115
1116 fn tlv_u64(attr_type: u16, val: u64) -> Vec<u8> {
1118 tlv(attr_type, &val.to_le_bytes())
1119 }
1120
1121 fn tlv_uuid(attr_type: u16, uuid: Uuid) -> Vec<u8> {
1123 tlv(attr_type, uuid.as_bytes())
1124 }
1125
1126 fn tlv_timespec(attr_type: u16, sec: u64, nsec: u32) -> Vec<u8> {
1128 let mut data = Vec::new();
1129 data.extend_from_slice(&sec.to_le_bytes());
1130 data.extend_from_slice(&nsec.to_le_bytes());
1131 tlv(attr_type, &data)
1132 }
1133
1134 #[test]
1137 fn valid_v1_header() {
1138 let mut stream = v1_header();
1139 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1141 let reader = StreamReader::new(Cursor::new(stream)).unwrap();
1142 assert_eq!(reader.version(), 1);
1143 }
1144
1145 #[test]
1146 fn valid_v2_header() {
1147 let mut buf = Vec::new();
1148 buf.extend_from_slice(SEND_STREAM_MAGIC);
1149 buf.extend_from_slice(&2u32.to_le_bytes());
1150 buf.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1151 let reader = StreamReader::new(Cursor::new(buf)).unwrap();
1152 assert_eq!(reader.version(), 2);
1153 }
1154
1155 #[test]
1156 fn bad_magic() {
1157 let buf = b"not-a-stream\0\x01\x00\x00\x00";
1158 let err = StreamReader::new(Cursor::new(buf.to_vec())).unwrap_err();
1159 assert!(
1160 format!("{err}").contains("bad magic"),
1161 "expected bad magic error, got: {err}"
1162 );
1163 }
1164
1165 #[test]
1166 fn unsupported_version_zero() {
1167 let mut buf = Vec::new();
1168 buf.extend_from_slice(SEND_STREAM_MAGIC);
1169 buf.extend_from_slice(&0u32.to_le_bytes());
1170 let err = StreamReader::new(Cursor::new(buf)).unwrap_err();
1171 assert!(format!("{err}").contains("unsupported"), "got: {err}");
1172 }
1173
1174 #[test]
1175 fn valid_v3_header() {
1176 let mut buf = Vec::new();
1177 buf.extend_from_slice(SEND_STREAM_MAGIC);
1178 buf.extend_from_slice(&3u32.to_le_bytes());
1179 buf.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1180 let reader = StreamReader::new(Cursor::new(buf)).unwrap();
1181 assert_eq!(reader.version(), 3);
1182 }
1183
1184 #[test]
1185 fn unsupported_version_four() {
1186 let mut buf = Vec::new();
1187 buf.extend_from_slice(SEND_STREAM_MAGIC);
1188 buf.extend_from_slice(&4u32.to_le_bytes());
1189 let err = StreamReader::new(Cursor::new(buf)).unwrap_err();
1190 assert!(format!("{err}").contains("unsupported"), "got: {err}");
1191 }
1192
1193 #[test]
1194 fn truncated_header() {
1195 let buf = b"btrfs-str"; let err = StreamReader::new(Cursor::new(buf.to_vec())).unwrap_err();
1197 assert!(matches!(err, StreamError::TruncatedHeader(_)), "got: {err}");
1198 }
1199
1200 #[test]
1203 fn end_command() {
1204 let mut stream = v1_header();
1205 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1206 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1207 let cmd = reader.next_command().unwrap().unwrap();
1208 assert!(matches!(cmd, StreamCommand::End));
1209 }
1210
1211 #[test]
1212 fn eof_after_end() {
1213 let mut stream = v1_header();
1214 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1215 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1216 reader.next_command().unwrap(); assert!(reader.next_command().unwrap().is_none()); }
1219
1220 #[test]
1223 fn corrupted_crc() {
1224 let mut stream = v1_header();
1225 let mut cmd = build_command(BTRFS_SEND_C_END, &[]);
1226 cmd[8] ^= 0xFF; stream.extend_from_slice(&cmd);
1228 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1229 let err = reader.next_command().unwrap_err();
1230 assert!(format!("{err}").contains("CRC mismatch"), "got: {err}");
1231 }
1232
1233 #[test]
1234 fn corrupted_payload() {
1235 let mut payload = Vec::new();
1236 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "test"));
1237 let mut stream = v1_header();
1238 let mut cmd = build_command(BTRFS_SEND_C_MKFILE, &payload);
1239 if cmd.len() > 11 {
1241 cmd[11] ^= 0xFF;
1242 }
1243 stream.extend_from_slice(&cmd);
1244 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1245 let err = reader.next_command().unwrap_err();
1246 assert!(format!("{err}").contains("CRC mismatch"), "got: {err}");
1247 }
1248
1249 #[test]
1252 fn mkfile_command() {
1253 let payload = tlv_string(BTRFS_SEND_A_PATH, "hello.txt");
1254 let mut stream = v1_header();
1255 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFILE, &payload));
1256 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1257 match reader.next_command().unwrap().unwrap() {
1258 StreamCommand::Mkfile { path } => assert_eq!(path, "hello.txt"),
1259 other => panic!("expected Mkfile, got {other:?}"),
1260 }
1261 }
1262
1263 #[test]
1264 fn mkdir_command() {
1265 let payload = tlv_string(BTRFS_SEND_A_PATH, "subdir");
1266 let mut stream = v1_header();
1267 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKDIR, &payload));
1268 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1269 match reader.next_command().unwrap().unwrap() {
1270 StreamCommand::Mkdir { path } => assert_eq!(path, "subdir"),
1271 other => panic!("expected Mkdir, got {other:?}"),
1272 }
1273 }
1274
1275 #[test]
1276 fn unlink_command() {
1277 let payload = tlv_string(BTRFS_SEND_A_PATH, "gone.txt");
1278 let mut stream = v1_header();
1279 stream.extend_from_slice(&build_command(BTRFS_SEND_C_UNLINK, &payload));
1280 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1281 match reader.next_command().unwrap().unwrap() {
1282 StreamCommand::Unlink { path } => assert_eq!(path, "gone.txt"),
1283 other => panic!("expected Unlink, got {other:?}"),
1284 }
1285 }
1286
1287 #[test]
1288 fn rmdir_command() {
1289 let payload = tlv_string(BTRFS_SEND_A_PATH, "old_dir");
1290 let mut stream = v1_header();
1291 stream.extend_from_slice(&build_command(BTRFS_SEND_C_RMDIR, &payload));
1292 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1293 match reader.next_command().unwrap().unwrap() {
1294 StreamCommand::Rmdir { path } => assert_eq!(path, "old_dir"),
1295 other => panic!("expected Rmdir, got {other:?}"),
1296 }
1297 }
1298
1299 #[test]
1302 fn rename_command() {
1303 let mut payload = Vec::new();
1304 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "old_name"));
1305 payload
1306 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_TO, "new_name"));
1307 let mut stream = v1_header();
1308 stream.extend_from_slice(&build_command(BTRFS_SEND_C_RENAME, &payload));
1309 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1310 match reader.next_command().unwrap().unwrap() {
1311 StreamCommand::Rename { from, to } => {
1312 assert_eq!(from, "old_name");
1313 assert_eq!(to, "new_name");
1314 }
1315 other => panic!("expected Rename, got {other:?}"),
1316 }
1317 }
1318
1319 #[test]
1320 fn symlink_command() {
1321 let mut payload = Vec::new();
1322 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "link"));
1323 payload
1324 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_LINK, "/target"));
1325 let mut stream = v1_header();
1326 stream
1327 .extend_from_slice(&build_command(BTRFS_SEND_C_SYMLINK, &payload));
1328 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1329 match reader.next_command().unwrap().unwrap() {
1330 StreamCommand::Symlink { path, target } => {
1331 assert_eq!(path, "link");
1332 assert_eq!(target, "/target");
1333 }
1334 other => panic!("expected Symlink, got {other:?}"),
1335 }
1336 }
1337
1338 #[test]
1339 fn chmod_command() {
1340 let mut payload = Vec::new();
1341 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file.sh"));
1342 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_MODE, 0o755));
1343 let mut stream = v1_header();
1344 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CHMOD, &payload));
1345 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1346 match reader.next_command().unwrap().unwrap() {
1347 StreamCommand::Chmod { path, mode } => {
1348 assert_eq!(path, "file.sh");
1349 assert_eq!(mode, 0o755);
1350 }
1351 other => panic!("expected Chmod, got {other:?}"),
1352 }
1353 }
1354
1355 #[test]
1356 fn chown_command() {
1357 let mut payload = Vec::new();
1358 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "owned"));
1359 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_UID, 1000));
1360 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_GID, 1000));
1361 let mut stream = v1_header();
1362 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CHOWN, &payload));
1363 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1364 match reader.next_command().unwrap().unwrap() {
1365 StreamCommand::Chown { path, uid, gid } => {
1366 assert_eq!(path, "owned");
1367 assert_eq!(uid, 1000);
1368 assert_eq!(gid, 1000);
1369 }
1370 other => panic!("expected Chown, got {other:?}"),
1371 }
1372 }
1373
1374 #[test]
1375 fn truncate_command() {
1376 let mut payload = Vec::new();
1377 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "shrink.bin"));
1378 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_SIZE, 4096));
1379 let mut stream = v1_header();
1380 stream
1381 .extend_from_slice(&build_command(BTRFS_SEND_C_TRUNCATE, &payload));
1382 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1383 match reader.next_command().unwrap().unwrap() {
1384 StreamCommand::Truncate { path, size } => {
1385 assert_eq!(path, "shrink.bin");
1386 assert_eq!(size, 4096);
1387 }
1388 other => panic!("expected Truncate, got {other:?}"),
1389 }
1390 }
1391
1392 #[test]
1393 fn write_command() {
1394 let file_data = b"hello world";
1395 let mut payload = Vec::new();
1396 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "data.bin"));
1397 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 512));
1398 payload.extend_from_slice(&tlv(BTRFS_SEND_A_DATA, file_data));
1399 let mut stream = v1_header();
1400 stream.extend_from_slice(&build_command(BTRFS_SEND_C_WRITE, &payload));
1401 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1402 match reader.next_command().unwrap().unwrap() {
1403 StreamCommand::Write { path, offset, data } => {
1404 assert_eq!(path, "data.bin");
1405 assert_eq!(offset, 512);
1406 assert_eq!(data, file_data);
1407 }
1408 other => panic!("expected Write, got {other:?}"),
1409 }
1410 }
1411
1412 #[test]
1413 fn set_xattr_command() {
1414 let xattr_value = b"\x01\x02\x03";
1415 let mut payload = Vec::new();
1416 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file"));
1417 payload.extend_from_slice(&tlv_string(
1418 BTRFS_SEND_A_XATTR_NAME,
1419 "user.test",
1420 ));
1421 payload.extend_from_slice(&tlv(BTRFS_SEND_A_XATTR_DATA, xattr_value));
1422 let mut stream = v1_header();
1423 stream.extend_from_slice(&build_command(
1424 BTRFS_SEND_C_SET_XATTR,
1425 &payload,
1426 ));
1427 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1428 match reader.next_command().unwrap().unwrap() {
1429 StreamCommand::SetXattr { path, name, data } => {
1430 assert_eq!(path, "file");
1431 assert_eq!(name, "user.test");
1432 assert_eq!(data, xattr_value);
1433 }
1434 other => panic!("expected SetXattr, got {other:?}"),
1435 }
1436 }
1437
1438 #[test]
1439 fn utimes_command() {
1440 let mut payload = Vec::new();
1441 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "timed"));
1442 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_ATIME, 1000, 111));
1443 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_MTIME, 2000, 222));
1444 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_CTIME, 3000, 333));
1445 let mut stream = v1_header();
1446 stream.extend_from_slice(&build_command(BTRFS_SEND_C_UTIMES, &payload));
1447 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1448 match reader.next_command().unwrap().unwrap() {
1449 StreamCommand::Utimes {
1450 path,
1451 atime,
1452 mtime,
1453 ctime,
1454 } => {
1455 assert_eq!(path, "timed");
1456 assert_eq!(atime.sec, 1000);
1457 assert_eq!(atime.nsec, 111);
1458 assert_eq!(mtime.sec, 2000);
1459 assert_eq!(mtime.nsec, 222);
1460 assert_eq!(ctime.sec, 3000);
1461 assert_eq!(ctime.nsec, 333);
1462 }
1463 other => panic!("expected Utimes, got {other:?}"),
1464 }
1465 }
1466
1467 #[test]
1470 fn subvol_command() {
1471 let uuid =
1472 Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
1473 let mut payload = Vec::new();
1474 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "my_subvol"));
1475 payload.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1476 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 42));
1477 let mut stream = v1_header();
1478 stream.extend_from_slice(&build_command(BTRFS_SEND_C_SUBVOL, &payload));
1479 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1480 match reader.next_command().unwrap().unwrap() {
1481 StreamCommand::Subvol {
1482 path,
1483 uuid: u,
1484 ctransid,
1485 } => {
1486 assert_eq!(path, "my_subvol");
1487 assert_eq!(u, uuid);
1488 assert_eq!(ctransid, 42);
1489 }
1490 other => panic!("expected Subvol, got {other:?}"),
1491 }
1492 }
1493
1494 #[test]
1495 fn snapshot_command() {
1496 let uuid =
1497 Uuid::parse_str("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee").unwrap();
1498 let clone_uuid =
1499 Uuid::parse_str("11111111-2222-3333-4444-555555555555").unwrap();
1500 let mut payload = Vec::new();
1501 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "my_snap"));
1502 payload.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1503 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 100));
1504 payload
1505 .extend_from_slice(&tlv_uuid(BTRFS_SEND_A_CLONE_UUID, clone_uuid));
1506 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_CTRANSID, 99));
1507 let mut stream = v1_header();
1508 stream
1509 .extend_from_slice(&build_command(BTRFS_SEND_C_SNAPSHOT, &payload));
1510 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1511 match reader.next_command().unwrap().unwrap() {
1512 StreamCommand::Snapshot {
1513 path,
1514 uuid: u,
1515 ctransid,
1516 clone_uuid: cu,
1517 clone_ctransid,
1518 } => {
1519 assert_eq!(path, "my_snap");
1520 assert_eq!(u, uuid);
1521 assert_eq!(ctransid, 100);
1522 assert_eq!(cu, clone_uuid);
1523 assert_eq!(clone_ctransid, 99);
1524 }
1525 other => panic!("expected Snapshot, got {other:?}"),
1526 }
1527 }
1528
1529 #[test]
1532 fn clone_command() {
1533 let clone_uuid =
1534 Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap();
1535 let mut payload = Vec::new();
1536 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "dest"));
1537 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 0));
1538 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_LEN, 4096));
1539 payload
1540 .extend_from_slice(&tlv_uuid(BTRFS_SEND_A_CLONE_UUID, clone_uuid));
1541 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_CTRANSID, 7));
1542 payload
1543 .extend_from_slice(&tlv_string(BTRFS_SEND_A_CLONE_PATH, "source"));
1544 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_OFFSET, 8192));
1545 let mut stream = v1_header();
1546 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CLONE, &payload));
1547 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1548 match reader.next_command().unwrap().unwrap() {
1549 StreamCommand::Clone {
1550 path,
1551 offset,
1552 len,
1553 clone_uuid: cu,
1554 clone_ctransid,
1555 clone_path,
1556 clone_offset,
1557 } => {
1558 assert_eq!(path, "dest");
1559 assert_eq!(offset, 0);
1560 assert_eq!(len, 4096);
1561 assert_eq!(cu, clone_uuid);
1562 assert_eq!(clone_ctransid, 7);
1563 assert_eq!(clone_path, "source");
1564 assert_eq!(clone_offset, 8192);
1565 }
1566 other => panic!("expected Clone, got {other:?}"),
1567 }
1568 }
1569
1570 #[test]
1573 fn multiple_commands_in_sequence() {
1574 let uuid = Uuid::nil();
1575 let mut stream = v1_header();
1576
1577 let mut p = Vec::new();
1579 p.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "vol"));
1580 p.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1581 p.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 1));
1582 stream.extend_from_slice(&build_command(BTRFS_SEND_C_SUBVOL, &p));
1583
1584 stream.extend_from_slice(&build_command(
1586 BTRFS_SEND_C_MKFILE,
1587 &tlv_string(BTRFS_SEND_A_PATH, "file.txt"),
1588 ));
1589
1590 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1592
1593 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1594
1595 assert!(matches!(
1596 reader.next_command().unwrap().unwrap(),
1597 StreamCommand::Subvol { .. }
1598 ));
1599 assert!(matches!(
1600 reader.next_command().unwrap().unwrap(),
1601 StreamCommand::Mkfile { .. }
1602 ));
1603 assert!(matches!(
1604 reader.next_command().unwrap().unwrap(),
1605 StreamCommand::End
1606 ));
1607 assert!(reader.next_command().unwrap().is_none());
1608 }
1609
1610 #[test]
1613 fn unknown_command_type() {
1614 let mut stream = v1_header();
1615 stream.extend_from_slice(&build_command(99, &[]));
1616 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1617 let err = reader.next_command().unwrap_err();
1618 assert!(
1619 format!("{err}").contains("unknown send stream command type 99"),
1620 "got: {err}"
1621 );
1622 }
1623
1624 #[test]
1625 fn missing_required_attribute() {
1626 let mut stream = v1_header();
1628 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFILE, &[]));
1629 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1630 let err = reader.next_command().unwrap_err();
1631 assert!(
1632 format!("{err}").contains("missing required attribute"),
1633 "got: {err}"
1634 );
1635 }
1636
1637 #[test]
1638 fn truncated_payload() {
1639 let mut stream = v1_header();
1640 let len = 100u32;
1643 let cmd = BTRFS_SEND_C_END;
1644 let mut crc_input = Vec::new();
1645 crc_input.extend_from_slice(&len.to_le_bytes());
1646 crc_input.extend_from_slice(&cmd.to_le_bytes());
1647 crc_input.extend_from_slice(&[0u8; 4]);
1648 let crc = crc32c::crc32c(&crc_input);
1649 stream.extend_from_slice(&len.to_le_bytes());
1650 stream.extend_from_slice(&cmd.to_le_bytes());
1651 stream.extend_from_slice(&crc.to_le_bytes());
1652 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1654 let err = reader.next_command().unwrap_err();
1655 assert!(format!("{err}").contains("truncated"), "got: {err}");
1656 }
1657
1658 #[test]
1659 fn into_inner_returns_reader() {
1660 let stream = v1_header();
1661 let cursor = Cursor::new(stream);
1662 let reader = StreamReader::new(cursor).unwrap();
1663 let _inner: Cursor<Vec<u8>> = reader.into_inner();
1664 }
1665
1666 #[test]
1669 fn update_extent_command() {
1670 let mut payload = Vec::new();
1671 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "extent.dat"));
1672 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 65536));
1673 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_SIZE, 131072));
1674 let mut stream = v1_header();
1675 stream.extend_from_slice(&build_command(
1676 BTRFS_SEND_C_UPDATE_EXTENT,
1677 &payload,
1678 ));
1679 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1680 match reader.next_command().unwrap().unwrap() {
1681 StreamCommand::UpdateExtent { path, offset, len } => {
1682 assert_eq!(path, "extent.dat");
1683 assert_eq!(offset, 65536);
1684 assert_eq!(len, 131072);
1685 }
1686 other => panic!("expected UpdateExtent, got {other:?}"),
1687 }
1688 }
1689
1690 #[test]
1693 fn mknod_command() {
1694 let mut payload = Vec::new();
1695 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "dev_node"));
1696 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_MODE, 0o660));
1697 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_RDEV, 0x0801)); let mut stream = v1_header();
1699 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKNOD, &payload));
1700 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1701 match reader.next_command().unwrap().unwrap() {
1702 StreamCommand::Mknod { path, mode, rdev } => {
1703 assert_eq!(path, "dev_node");
1704 assert_eq!(mode, 0o660);
1705 assert_eq!(rdev, 0x0801);
1706 }
1707 other => panic!("expected Mknod, got {other:?}"),
1708 }
1709 }
1710
1711 #[test]
1712 fn mkfifo_command() {
1713 let payload = tlv_string(BTRFS_SEND_A_PATH, "my_fifo");
1714 let mut stream = v1_header();
1715 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFIFO, &payload));
1716 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1717 match reader.next_command().unwrap().unwrap() {
1718 StreamCommand::Mkfifo { path } => assert_eq!(path, "my_fifo"),
1719 other => panic!("expected Mkfifo, got {other:?}"),
1720 }
1721 }
1722
1723 #[test]
1724 fn mksock_command() {
1725 let payload = tlv_string(BTRFS_SEND_A_PATH, "my_sock");
1726 let mut stream = v1_header();
1727 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKSOCK, &payload));
1728 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1729 match reader.next_command().unwrap().unwrap() {
1730 StreamCommand::Mksock { path } => assert_eq!(path, "my_sock"),
1731 other => panic!("expected Mksock, got {other:?}"),
1732 }
1733 }
1734
1735 #[test]
1738 fn link_command() {
1739 let mut payload = Vec::new();
1740 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "hardlink"));
1741 payload
1742 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_LINK, "original"));
1743 let mut stream = v1_header();
1744 stream.extend_from_slice(&build_command(BTRFS_SEND_C_LINK, &payload));
1745 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1746 match reader.next_command().unwrap().unwrap() {
1747 StreamCommand::Link { path, target } => {
1748 assert_eq!(path, "hardlink");
1749 assert_eq!(target, "original");
1750 }
1751 other => panic!("expected Link, got {other:?}"),
1752 }
1753 }
1754
1755 #[test]
1756 fn remove_xattr_command() {
1757 let mut payload = Vec::new();
1758 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file"));
1759 payload.extend_from_slice(&tlv_string(
1760 BTRFS_SEND_A_XATTR_NAME,
1761 "user.old",
1762 ));
1763 let mut stream = v1_header();
1764 stream.extend_from_slice(&build_command(
1765 BTRFS_SEND_C_REMOVE_XATTR,
1766 &payload,
1767 ));
1768 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1769 match reader.next_command().unwrap().unwrap() {
1770 StreamCommand::RemoveXattr { path, name } => {
1771 assert_eq!(path, "file");
1772 assert_eq!(name, "user.old");
1773 }
1774 other => panic!("expected RemoveXattr, got {other:?}"),
1775 }
1776 }
1777}