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> {
294 let mut header = [0u8; STREAM_HEADER_LEN];
295 reader
296 .read_exact(&mut header)
297 .map_err(StreamError::TruncatedHeader)?;
298
299 if &header[..SEND_STREAM_MAGIC_LEN] != SEND_STREAM_MAGIC {
300 return Err(StreamError::BadMagic);
301 }
302
303 let version = u32::from_le_bytes(
304 header[SEND_STREAM_MAGIC_LEN..STREAM_HEADER_LEN]
305 .try_into()
306 .unwrap(),
307 );
308
309 if version == 0 || version > 3 {
310 return Err(StreamError::UnsupportedVersion(version));
311 }
312
313 Ok(Self {
314 reader,
315 version,
316 buf: Vec::with_capacity(64 * 1024),
317 })
318 }
319
320 pub fn version(&self) -> u32 {
322 self.version
323 }
324
325 pub fn into_inner(self) -> R {
327 self.reader
328 }
329
330 pub fn next_command(&mut self) -> Result<Option<StreamCommand>> {
335 let mut cmd_hdr = [0u8; CMD_HEADER_LEN];
337 if !read_exact_or_eof(&mut self.reader, &mut cmd_hdr)? {
338 return Ok(None); }
340
341 let payload_len =
342 u32::from_le_bytes(cmd_hdr[0..4].try_into().unwrap()) as usize;
343 let cmd = u16::from_le_bytes(cmd_hdr[4..6].try_into().unwrap());
344 let expected_crc =
345 u32::from_le_bytes(cmd_hdr[6..10].try_into().unwrap());
346
347 self.buf.resize(payload_len, 0);
349 self.reader
350 .read_exact(&mut self.buf)
351 .map_err(StreamError::TruncatedPayload)?;
352
353 let mut crc_buf = Vec::with_capacity(CMD_HEADER_LEN + payload_len);
359 crc_buf.extend_from_slice(&cmd_hdr[0..6]); crc_buf.extend_from_slice(&[0u8; 4]); crc_buf.extend_from_slice(&self.buf);
362 let computed_crc = !crc32c::crc32c_append(!0, &crc_buf);
363 if computed_crc != expected_crc {
364 return Err(StreamError::CrcMismatch {
365 cmd,
366 expected: expected_crc,
367 computed: computed_crc,
368 });
369 }
370
371 let attrs = parse_tlv_attrs(&self.buf, self.version)?;
373
374 match cmd {
376 BTRFS_SEND_C_SUBVOL => Ok(Some(StreamCommand::Subvol {
377 path: attr_string(
378 &self.buf,
379 &attrs,
380 BTRFS_SEND_A_PATH,
381 "path",
382 )?,
383 uuid: attr_uuid(&self.buf, &attrs, BTRFS_SEND_A_UUID, "uuid")?,
384 ctransid: attr_u64(
385 &self.buf,
386 &attrs,
387 BTRFS_SEND_A_CTRANSID,
388 "ctransid",
389 )?,
390 })),
391 BTRFS_SEND_C_SNAPSHOT => Ok(Some(StreamCommand::Snapshot {
392 path: attr_string(
393 &self.buf,
394 &attrs,
395 BTRFS_SEND_A_PATH,
396 "path",
397 )?,
398 uuid: attr_uuid(&self.buf, &attrs, BTRFS_SEND_A_UUID, "uuid")?,
399 ctransid: attr_u64(
400 &self.buf,
401 &attrs,
402 BTRFS_SEND_A_CTRANSID,
403 "ctransid",
404 )?,
405 clone_uuid: attr_uuid(
406 &self.buf,
407 &attrs,
408 BTRFS_SEND_A_CLONE_UUID,
409 "clone_uuid",
410 )?,
411 clone_ctransid: attr_u64(
412 &self.buf,
413 &attrs,
414 BTRFS_SEND_A_CLONE_CTRANSID,
415 "clone_ctransid",
416 )?,
417 })),
418 BTRFS_SEND_C_MKFILE => Ok(Some(StreamCommand::Mkfile {
419 path: attr_string(
420 &self.buf,
421 &attrs,
422 BTRFS_SEND_A_PATH,
423 "path",
424 )?,
425 })),
426 BTRFS_SEND_C_MKDIR => Ok(Some(StreamCommand::Mkdir {
427 path: attr_string(
428 &self.buf,
429 &attrs,
430 BTRFS_SEND_A_PATH,
431 "path",
432 )?,
433 })),
434 BTRFS_SEND_C_MKNOD => Ok(Some(StreamCommand::Mknod {
435 path: attr_string(
436 &self.buf,
437 &attrs,
438 BTRFS_SEND_A_PATH,
439 "path",
440 )?,
441 mode: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_MODE, "mode")?,
442 rdev: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_RDEV, "rdev")?,
443 })),
444 BTRFS_SEND_C_MKFIFO => Ok(Some(StreamCommand::Mkfifo {
445 path: attr_string(
446 &self.buf,
447 &attrs,
448 BTRFS_SEND_A_PATH,
449 "path",
450 )?,
451 })),
452 BTRFS_SEND_C_MKSOCK => Ok(Some(StreamCommand::Mksock {
453 path: attr_string(
454 &self.buf,
455 &attrs,
456 BTRFS_SEND_A_PATH,
457 "path",
458 )?,
459 })),
460 BTRFS_SEND_C_SYMLINK => Ok(Some(StreamCommand::Symlink {
461 path: attr_string(
462 &self.buf,
463 &attrs,
464 BTRFS_SEND_A_PATH,
465 "path",
466 )?,
467 target: attr_string(
468 &self.buf,
469 &attrs,
470 BTRFS_SEND_A_PATH_LINK,
471 "link_target",
472 )?,
473 })),
474 BTRFS_SEND_C_RENAME => Ok(Some(StreamCommand::Rename {
475 from: attr_string(
476 &self.buf,
477 &attrs,
478 BTRFS_SEND_A_PATH,
479 "path",
480 )?,
481 to: attr_string(
482 &self.buf,
483 &attrs,
484 BTRFS_SEND_A_PATH_TO,
485 "path_to",
486 )?,
487 })),
488 BTRFS_SEND_C_LINK => Ok(Some(StreamCommand::Link {
489 path: attr_string(
490 &self.buf,
491 &attrs,
492 BTRFS_SEND_A_PATH,
493 "path",
494 )?,
495 target: attr_string(
496 &self.buf,
497 &attrs,
498 BTRFS_SEND_A_PATH_LINK,
499 "link_target",
500 )?,
501 })),
502 BTRFS_SEND_C_UNLINK => Ok(Some(StreamCommand::Unlink {
503 path: attr_string(
504 &self.buf,
505 &attrs,
506 BTRFS_SEND_A_PATH,
507 "path",
508 )?,
509 })),
510 BTRFS_SEND_C_RMDIR => Ok(Some(StreamCommand::Rmdir {
511 path: attr_string(
512 &self.buf,
513 &attrs,
514 BTRFS_SEND_A_PATH,
515 "path",
516 )?,
517 })),
518 BTRFS_SEND_C_SET_XATTR => Ok(Some(StreamCommand::SetXattr {
519 path: attr_string(
520 &self.buf,
521 &attrs,
522 BTRFS_SEND_A_PATH,
523 "path",
524 )?,
525 name: attr_string(
526 &self.buf,
527 &attrs,
528 BTRFS_SEND_A_XATTR_NAME,
529 "xattr_name",
530 )?,
531 data: attr_data(
532 &self.buf,
533 &attrs,
534 BTRFS_SEND_A_XATTR_DATA,
535 "xattr_data",
536 )?,
537 })),
538 BTRFS_SEND_C_REMOVE_XATTR => Ok(Some(StreamCommand::RemoveXattr {
539 path: attr_string(
540 &self.buf,
541 &attrs,
542 BTRFS_SEND_A_PATH,
543 "path",
544 )?,
545 name: attr_string(
546 &self.buf,
547 &attrs,
548 BTRFS_SEND_A_XATTR_NAME,
549 "xattr_name",
550 )?,
551 })),
552 BTRFS_SEND_C_WRITE => Ok(Some(StreamCommand::Write {
553 path: attr_string(
554 &self.buf,
555 &attrs,
556 BTRFS_SEND_A_PATH,
557 "path",
558 )?,
559 offset: attr_u64(
560 &self.buf,
561 &attrs,
562 BTRFS_SEND_A_FILE_OFFSET,
563 "file_offset",
564 )?,
565 data: attr_data(&self.buf, &attrs, BTRFS_SEND_A_DATA, "data")?,
566 })),
567 BTRFS_SEND_C_CLONE => Ok(Some(StreamCommand::Clone {
568 path: attr_string(
569 &self.buf,
570 &attrs,
571 BTRFS_SEND_A_PATH,
572 "path",
573 )?,
574 offset: attr_u64(
575 &self.buf,
576 &attrs,
577 BTRFS_SEND_A_FILE_OFFSET,
578 "file_offset",
579 )?,
580 len: attr_u64(
581 &self.buf,
582 &attrs,
583 BTRFS_SEND_A_CLONE_LEN,
584 "clone_len",
585 )?,
586 clone_uuid: attr_uuid(
587 &self.buf,
588 &attrs,
589 BTRFS_SEND_A_CLONE_UUID,
590 "clone_uuid",
591 )?,
592 clone_ctransid: attr_u64(
593 &self.buf,
594 &attrs,
595 BTRFS_SEND_A_CLONE_CTRANSID,
596 "clone_ctransid",
597 )?,
598 clone_path: attr_string(
599 &self.buf,
600 &attrs,
601 BTRFS_SEND_A_CLONE_PATH,
602 "clone_path",
603 )?,
604 clone_offset: attr_u64(
605 &self.buf,
606 &attrs,
607 BTRFS_SEND_A_CLONE_OFFSET,
608 "clone_offset",
609 )?,
610 })),
611 BTRFS_SEND_C_TRUNCATE => Ok(Some(StreamCommand::Truncate {
612 path: attr_string(
613 &self.buf,
614 &attrs,
615 BTRFS_SEND_A_PATH,
616 "path",
617 )?,
618 size: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_SIZE, "size")?,
619 })),
620 BTRFS_SEND_C_CHMOD => Ok(Some(StreamCommand::Chmod {
621 path: attr_string(
622 &self.buf,
623 &attrs,
624 BTRFS_SEND_A_PATH,
625 "path",
626 )?,
627 mode: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_MODE, "mode")?,
628 })),
629 BTRFS_SEND_C_CHOWN => Ok(Some(StreamCommand::Chown {
630 path: attr_string(
631 &self.buf,
632 &attrs,
633 BTRFS_SEND_A_PATH,
634 "path",
635 )?,
636 uid: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_UID, "uid")?,
637 gid: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_GID, "gid")?,
638 })),
639 BTRFS_SEND_C_UTIMES => Ok(Some(StreamCommand::Utimes {
640 path: attr_string(
641 &self.buf,
642 &attrs,
643 BTRFS_SEND_A_PATH,
644 "path",
645 )?,
646 atime: attr_timespec(
647 &self.buf,
648 &attrs,
649 BTRFS_SEND_A_ATIME,
650 "atime",
651 )?,
652 mtime: attr_timespec(
653 &self.buf,
654 &attrs,
655 BTRFS_SEND_A_MTIME,
656 "mtime",
657 )?,
658 ctime: attr_timespec(
659 &self.buf,
660 &attrs,
661 BTRFS_SEND_A_CTIME,
662 "ctime",
663 )?,
664 })),
665 BTRFS_SEND_C_UPDATE_EXTENT => {
666 Ok(Some(StreamCommand::UpdateExtent {
667 path: attr_string(
668 &self.buf,
669 &attrs,
670 BTRFS_SEND_A_PATH,
671 "path",
672 )?,
673 offset: attr_u64(
674 &self.buf,
675 &attrs,
676 BTRFS_SEND_A_FILE_OFFSET,
677 "file_offset",
678 )?,
679 len: attr_u64(
680 &self.buf,
681 &attrs,
682 BTRFS_SEND_A_SIZE,
683 "size",
684 )?,
685 }))
686 }
687 BTRFS_SEND_C_FALLOCATE => Ok(Some(StreamCommand::Fallocate {
688 path: attr_string(
689 &self.buf,
690 &attrs,
691 BTRFS_SEND_A_PATH,
692 "path",
693 )?,
694 mode: attr_u32(
695 &self.buf,
696 &attrs,
697 BTRFS_SEND_A_FALLOCATE_MODE,
698 "fallocate_mode",
699 )?,
700 offset: attr_u64(
701 &self.buf,
702 &attrs,
703 BTRFS_SEND_A_FILE_OFFSET,
704 "file_offset",
705 )?,
706 len: attr_u64(&self.buf, &attrs, BTRFS_SEND_A_SIZE, "size")?,
707 })),
708 BTRFS_SEND_C_FILEATTR => Ok(Some(StreamCommand::Fileattr {
709 path: attr_string(
710 &self.buf,
711 &attrs,
712 BTRFS_SEND_A_PATH,
713 "path",
714 )?,
715 attr: attr_u64(
716 &self.buf,
717 &attrs,
718 BTRFS_SEND_A_FILEATTR,
719 "fileattr",
720 )?,
721 })),
722 BTRFS_SEND_C_ENCODED_WRITE => {
723 Ok(Some(StreamCommand::EncodedWrite {
724 path: attr_string(
725 &self.buf,
726 &attrs,
727 BTRFS_SEND_A_PATH,
728 "path",
729 )?,
730 offset: attr_u64(
731 &self.buf,
732 &attrs,
733 BTRFS_SEND_A_FILE_OFFSET,
734 "file_offset",
735 )?,
736 unencoded_file_len: attr_u64(
737 &self.buf,
738 &attrs,
739 BTRFS_SEND_A_UNENCODED_FILE_LEN,
740 "unencoded_file_len",
741 )?,
742 unencoded_len: attr_u64(
743 &self.buf,
744 &attrs,
745 BTRFS_SEND_A_UNENCODED_LEN,
746 "unencoded_len",
747 )?,
748 unencoded_offset: attr_u64(
749 &self.buf,
750 &attrs,
751 BTRFS_SEND_A_UNENCODED_OFFSET,
752 "unencoded_offset",
753 )?,
754 compression: attr_opt_u32(
756 &self.buf,
757 &attrs,
758 BTRFS_SEND_A_COMPRESSION,
759 0,
760 ),
761 encryption: attr_opt_u32(
762 &self.buf,
763 &attrs,
764 BTRFS_SEND_A_ENCRYPTION,
765 0,
766 ),
767 data: attr_data(
768 &self.buf,
769 &attrs,
770 BTRFS_SEND_A_DATA,
771 "data",
772 )?,
773 }))
774 }
775 BTRFS_SEND_C_ENABLE_VERITY => {
776 Ok(Some(StreamCommand::EnableVerity {
777 path: attr_string(
778 &self.buf,
779 &attrs,
780 BTRFS_SEND_A_PATH,
781 "path",
782 )?,
783 algorithm: attr_u8(
784 &self.buf,
785 &attrs,
786 BTRFS_SEND_A_VERITY_ALGORITHM,
787 "verity_algorithm",
788 )?,
789 block_size: attr_u32(
790 &self.buf,
791 &attrs,
792 BTRFS_SEND_A_VERITY_BLOCK_SIZE,
793 "verity_block_size",
794 )?,
795 salt: attr_data(
796 &self.buf,
797 &attrs,
798 BTRFS_SEND_A_VERITY_SALT_DATA,
799 "verity_salt",
800 )?,
801 sig: attr_data(
802 &self.buf,
803 &attrs,
804 BTRFS_SEND_A_VERITY_SIG_DATA,
805 "verity_sig",
806 )?,
807 }))
808 }
809 BTRFS_SEND_C_END => Ok(Some(StreamCommand::End)),
810 _ => Err(StreamError::UnknownCommand(cmd)),
811 }
812 }
813}
814
815fn read_exact_or_eof(reader: &mut impl Read, buf: &mut [u8]) -> Result<bool> {
817 let mut pos = 0;
818 while pos < buf.len() {
819 match reader.read(&mut buf[pos..]) {
820 Ok(0) => {
821 if pos == 0 {
822 return Ok(false);
823 }
824 return Err(StreamError::TruncatedPayload(
825 std::io::Error::new(
826 std::io::ErrorKind::UnexpectedEof,
827 format!("unexpected EOF after {pos} bytes"),
828 ),
829 ));
830 }
831 Ok(n) => pos += n,
832 Err(e) => return Err(StreamError::TruncatedPayload(e)),
833 }
834 }
835 Ok(true)
836}
837
838type AttrTable = [Option<(usize, usize)>; MAX_ATTRS];
841
842fn parse_tlv_attrs(payload: &[u8], version: u32) -> Result<AttrTable> {
844 let mut attrs: AttrTable = [None; MAX_ATTRS];
845 let mut pos = 0;
846
847 while pos < payload.len() {
848 if pos + 2 > payload.len() {
849 return Err(StreamError::TruncatedTlv {
850 detail: "not enough bytes for type field".into(),
851 });
852 }
853 let tlv_type =
854 u16::from_le_bytes(payload[pos..pos + 2].try_into().unwrap());
855 pos += 2;
856
857 if tlv_type == 0 || tlv_type as usize > MAX_ATTRS {
858 return Err(StreamError::InvalidTlvType(tlv_type));
859 }
860
861 let tlv_len = if version >= 2 && tlv_type == BTRFS_SEND_A_DATA {
864 payload.len() - pos
865 } else {
866 if pos + 2 > payload.len() {
867 return Err(StreamError::TruncatedTlv {
868 detail: "not enough bytes for length field".into(),
869 });
870 }
871 let len =
872 u16::from_le_bytes(payload[pos..pos + 2].try_into().unwrap())
873 as usize;
874 pos += 2;
875 len
876 };
877
878 if pos + tlv_len > payload.len() {
879 return Err(StreamError::TruncatedTlv {
880 detail: format!(
881 "attribute type {tlv_type} needs {tlv_len} bytes but only {} remain",
882 payload.len() - pos
883 ),
884 });
885 }
886
887 attrs[(tlv_type - 1) as usize] = Some((pos, tlv_len));
888 pos += tlv_len;
889 }
890
891 Ok(attrs)
892}
893
894fn get_attr<'a>(
895 buf: &'a [u8],
896 attrs: &AttrTable,
897 attr_type: u16,
898 name: &'static str,
899) -> Result<&'a [u8]> {
900 let (offset, len) = attrs[(attr_type - 1) as usize]
901 .ok_or(StreamError::MissingAttribute(name))?;
902 Ok(&buf[offset..offset + len])
903}
904
905fn attr_u64(
906 buf: &[u8],
907 attrs: &AttrTable,
908 attr_type: u16,
909 name: &'static str,
910) -> Result<u64> {
911 let data = get_attr(buf, attrs, attr_type, name)?;
912 if data.len() < 8 {
913 return Err(StreamError::InvalidAttribute {
914 name,
915 detail: format!("too short for u64: {} bytes", data.len()),
916 });
917 }
918 Ok(u64::from_le_bytes(data[0..8].try_into().unwrap()))
919}
920
921fn attr_string(
922 buf: &[u8],
923 attrs: &AttrTable,
924 attr_type: u16,
925 name: &'static str,
926) -> Result<String> {
927 let data = get_attr(buf, attrs, attr_type, name)?;
928 let s = if data.last() == Some(&0) {
930 &data[..data.len() - 1]
931 } else {
932 data
933 };
934 String::from_utf8(s.to_vec()).map_err(|_| StreamError::InvalidAttribute {
935 name,
936 detail: "not valid UTF-8".into(),
937 })
938}
939
940fn attr_uuid(
941 buf: &[u8],
942 attrs: &AttrTable,
943 attr_type: u16,
944 name: &'static str,
945) -> Result<Uuid> {
946 let data = get_attr(buf, attrs, attr_type, name)?;
947 if data.len() < 16 {
948 return Err(StreamError::InvalidAttribute {
949 name,
950 detail: format!("too short for UUID: {} bytes", data.len()),
951 });
952 }
953 Ok(Uuid::from_bytes(data[0..16].try_into().unwrap()))
954}
955
956fn attr_timespec(
957 buf: &[u8],
958 attrs: &AttrTable,
959 attr_type: u16,
960 name: &'static str,
961) -> Result<Timespec> {
962 let data = get_attr(buf, attrs, attr_type, name)?;
963 if data.len() < 12 {
964 return Err(StreamError::InvalidAttribute {
965 name,
966 detail: format!("too short for timespec: {} bytes", data.len()),
967 });
968 }
969 Ok(Timespec {
970 sec: u64::from_le_bytes(data[0..8].try_into().unwrap()),
971 nsec: u32::from_le_bytes(data[8..12].try_into().unwrap()),
972 })
973}
974
975fn attr_u32(
976 buf: &[u8],
977 attrs: &AttrTable,
978 attr_type: u16,
979 name: &'static str,
980) -> Result<u32> {
981 let data = get_attr(buf, attrs, attr_type, name)?;
982 if data.len() < 4 {
983 return Err(StreamError::InvalidAttribute {
984 name,
985 detail: format!("too short for u32: {} bytes", data.len()),
986 });
987 }
988 Ok(u32::from_le_bytes(data[0..4].try_into().unwrap()))
989}
990
991fn attr_u8(
992 buf: &[u8],
993 attrs: &AttrTable,
994 attr_type: u16,
995 name: &'static str,
996) -> Result<u8> {
997 let data = get_attr(buf, attrs, attr_type, name)?;
998 if data.is_empty() {
999 return Err(StreamError::InvalidAttribute {
1000 name,
1001 detail: "is empty".into(),
1002 });
1003 }
1004 Ok(data[0])
1005}
1006
1007fn get_attr_opt<'a>(
1010 buf: &'a [u8],
1011 attrs: &AttrTable,
1012 attr_type: u16,
1013) -> Option<&'a [u8]> {
1014 let (offset, len) = attrs[(attr_type - 1) as usize]?;
1015 Some(&buf[offset..offset + len])
1016}
1017
1018fn attr_opt_u32(
1019 buf: &[u8],
1020 attrs: &AttrTable,
1021 attr_type: u16,
1022 default: u32,
1023) -> u32 {
1024 match get_attr_opt(buf, attrs, attr_type) {
1025 Some(data) if data.len() >= 4 => {
1026 u32::from_le_bytes(data[0..4].try_into().unwrap())
1027 }
1028 _ => default,
1029 }
1030}
1031
1032fn attr_data(
1033 buf: &[u8],
1034 attrs: &AttrTable,
1035 attr_type: u16,
1036 name: &'static str,
1037) -> Result<Vec<u8>> {
1038 let data = get_attr(buf, attrs, attr_type, name)?;
1039 Ok(data.to_vec())
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use super::*;
1045 use std::io::Cursor;
1046
1047 fn v1_header() -> Vec<u8> {
1049 let mut buf = Vec::new();
1050 buf.extend_from_slice(SEND_STREAM_MAGIC);
1051 buf.extend_from_slice(&1u32.to_le_bytes());
1052 buf
1053 }
1054
1055 fn build_command(cmd: u16, payload: &[u8]) -> Vec<u8> {
1058 let len = payload.len() as u32;
1059 let mut crc_input = Vec::new();
1062 crc_input.extend_from_slice(&len.to_le_bytes());
1063 crc_input.extend_from_slice(&cmd.to_le_bytes());
1064 crc_input.extend_from_slice(&[0u8; 4]); crc_input.extend_from_slice(payload);
1066 let crc = !crc32c::crc32c_append(!0, &crc_input);
1067
1068 let mut out = Vec::new();
1069 out.extend_from_slice(&len.to_le_bytes());
1070 out.extend_from_slice(&cmd.to_le_bytes());
1071 out.extend_from_slice(&crc.to_le_bytes());
1072 out.extend_from_slice(payload);
1073 out
1074 }
1075
1076 fn tlv(attr_type: u16, data: &[u8]) -> Vec<u8> {
1078 let mut buf = Vec::new();
1079 buf.extend_from_slice(&attr_type.to_le_bytes());
1080 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
1081 buf.extend_from_slice(data);
1082 buf
1083 }
1084
1085 fn tlv_string(attr_type: u16, s: &str) -> Vec<u8> {
1087 let mut data = s.as_bytes().to_vec();
1088 data.push(0); tlv(attr_type, &data)
1090 }
1091
1092 fn tlv_u64(attr_type: u16, val: u64) -> Vec<u8> {
1094 tlv(attr_type, &val.to_le_bytes())
1095 }
1096
1097 fn tlv_uuid(attr_type: u16, uuid: Uuid) -> Vec<u8> {
1099 tlv(attr_type, uuid.as_bytes())
1100 }
1101
1102 fn tlv_timespec(attr_type: u16, sec: u64, nsec: u32) -> Vec<u8> {
1104 let mut data = Vec::new();
1105 data.extend_from_slice(&sec.to_le_bytes());
1106 data.extend_from_slice(&nsec.to_le_bytes());
1107 tlv(attr_type, &data)
1108 }
1109
1110 #[test]
1113 fn valid_v1_header() {
1114 let mut stream = v1_header();
1115 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1117 let reader = StreamReader::new(Cursor::new(stream)).unwrap();
1118 assert_eq!(reader.version(), 1);
1119 }
1120
1121 #[test]
1122 fn valid_v2_header() {
1123 let mut buf = Vec::new();
1124 buf.extend_from_slice(SEND_STREAM_MAGIC);
1125 buf.extend_from_slice(&2u32.to_le_bytes());
1126 buf.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1127 let reader = StreamReader::new(Cursor::new(buf)).unwrap();
1128 assert_eq!(reader.version(), 2);
1129 }
1130
1131 #[test]
1132 fn bad_magic() {
1133 let buf = b"not-a-stream\0\x01\x00\x00\x00";
1134 let err = StreamReader::new(Cursor::new(buf.to_vec())).unwrap_err();
1135 assert!(
1136 format!("{err}").contains("bad magic"),
1137 "expected bad magic error, got: {err}"
1138 );
1139 }
1140
1141 #[test]
1142 fn unsupported_version_zero() {
1143 let mut buf = Vec::new();
1144 buf.extend_from_slice(SEND_STREAM_MAGIC);
1145 buf.extend_from_slice(&0u32.to_le_bytes());
1146 let err = StreamReader::new(Cursor::new(buf)).unwrap_err();
1147 assert!(format!("{err}").contains("unsupported"), "got: {err}");
1148 }
1149
1150 #[test]
1151 fn valid_v3_header() {
1152 let mut buf = Vec::new();
1153 buf.extend_from_slice(SEND_STREAM_MAGIC);
1154 buf.extend_from_slice(&3u32.to_le_bytes());
1155 buf.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1156 let reader = StreamReader::new(Cursor::new(buf)).unwrap();
1157 assert_eq!(reader.version(), 3);
1158 }
1159
1160 #[test]
1161 fn unsupported_version_four() {
1162 let mut buf = Vec::new();
1163 buf.extend_from_slice(SEND_STREAM_MAGIC);
1164 buf.extend_from_slice(&4u32.to_le_bytes());
1165 let err = StreamReader::new(Cursor::new(buf)).unwrap_err();
1166 assert!(format!("{err}").contains("unsupported"), "got: {err}");
1167 }
1168
1169 #[test]
1170 fn truncated_header() {
1171 let buf = b"btrfs-str"; let err = StreamReader::new(Cursor::new(buf.to_vec())).unwrap_err();
1173 assert!(matches!(err, StreamError::TruncatedHeader(_)), "got: {err}");
1174 }
1175
1176 #[test]
1179 fn end_command() {
1180 let mut stream = v1_header();
1181 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1182 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1183 let cmd = reader.next_command().unwrap().unwrap();
1184 assert!(matches!(cmd, StreamCommand::End));
1185 }
1186
1187 #[test]
1188 fn eof_after_end() {
1189 let mut stream = v1_header();
1190 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1191 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1192 reader.next_command().unwrap(); assert!(reader.next_command().unwrap().is_none()); }
1195
1196 #[test]
1199 fn corrupted_crc() {
1200 let mut stream = v1_header();
1201 let mut cmd = build_command(BTRFS_SEND_C_END, &[]);
1202 cmd[8] ^= 0xFF; stream.extend_from_slice(&cmd);
1204 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1205 let err = reader.next_command().unwrap_err();
1206 assert!(format!("{err}").contains("CRC mismatch"), "got: {err}");
1207 }
1208
1209 #[test]
1210 fn corrupted_payload() {
1211 let mut payload = Vec::new();
1212 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "test"));
1213 let mut stream = v1_header();
1214 let mut cmd = build_command(BTRFS_SEND_C_MKFILE, &payload);
1215 if cmd.len() > 11 {
1217 cmd[11] ^= 0xFF;
1218 }
1219 stream.extend_from_slice(&cmd);
1220 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1221 let err = reader.next_command().unwrap_err();
1222 assert!(format!("{err}").contains("CRC mismatch"), "got: {err}");
1223 }
1224
1225 #[test]
1228 fn mkfile_command() {
1229 let payload = tlv_string(BTRFS_SEND_A_PATH, "hello.txt");
1230 let mut stream = v1_header();
1231 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFILE, &payload));
1232 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1233 match reader.next_command().unwrap().unwrap() {
1234 StreamCommand::Mkfile { path } => assert_eq!(path, "hello.txt"),
1235 other => panic!("expected Mkfile, got {other:?}"),
1236 }
1237 }
1238
1239 #[test]
1240 fn mkdir_command() {
1241 let payload = tlv_string(BTRFS_SEND_A_PATH, "subdir");
1242 let mut stream = v1_header();
1243 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKDIR, &payload));
1244 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1245 match reader.next_command().unwrap().unwrap() {
1246 StreamCommand::Mkdir { path } => assert_eq!(path, "subdir"),
1247 other => panic!("expected Mkdir, got {other:?}"),
1248 }
1249 }
1250
1251 #[test]
1252 fn unlink_command() {
1253 let payload = tlv_string(BTRFS_SEND_A_PATH, "gone.txt");
1254 let mut stream = v1_header();
1255 stream.extend_from_slice(&build_command(BTRFS_SEND_C_UNLINK, &payload));
1256 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1257 match reader.next_command().unwrap().unwrap() {
1258 StreamCommand::Unlink { path } => assert_eq!(path, "gone.txt"),
1259 other => panic!("expected Unlink, got {other:?}"),
1260 }
1261 }
1262
1263 #[test]
1264 fn rmdir_command() {
1265 let payload = tlv_string(BTRFS_SEND_A_PATH, "old_dir");
1266 let mut stream = v1_header();
1267 stream.extend_from_slice(&build_command(BTRFS_SEND_C_RMDIR, &payload));
1268 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1269 match reader.next_command().unwrap().unwrap() {
1270 StreamCommand::Rmdir { path } => assert_eq!(path, "old_dir"),
1271 other => panic!("expected Rmdir, got {other:?}"),
1272 }
1273 }
1274
1275 #[test]
1278 fn rename_command() {
1279 let mut payload = Vec::new();
1280 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "old_name"));
1281 payload
1282 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_TO, "new_name"));
1283 let mut stream = v1_header();
1284 stream.extend_from_slice(&build_command(BTRFS_SEND_C_RENAME, &payload));
1285 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1286 match reader.next_command().unwrap().unwrap() {
1287 StreamCommand::Rename { from, to } => {
1288 assert_eq!(from, "old_name");
1289 assert_eq!(to, "new_name");
1290 }
1291 other => panic!("expected Rename, got {other:?}"),
1292 }
1293 }
1294
1295 #[test]
1296 fn symlink_command() {
1297 let mut payload = Vec::new();
1298 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "link"));
1299 payload
1300 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_LINK, "/target"));
1301 let mut stream = v1_header();
1302 stream
1303 .extend_from_slice(&build_command(BTRFS_SEND_C_SYMLINK, &payload));
1304 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1305 match reader.next_command().unwrap().unwrap() {
1306 StreamCommand::Symlink { path, target } => {
1307 assert_eq!(path, "link");
1308 assert_eq!(target, "/target");
1309 }
1310 other => panic!("expected Symlink, got {other:?}"),
1311 }
1312 }
1313
1314 #[test]
1315 fn chmod_command() {
1316 let mut payload = Vec::new();
1317 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file.sh"));
1318 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_MODE, 0o755));
1319 let mut stream = v1_header();
1320 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CHMOD, &payload));
1321 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1322 match reader.next_command().unwrap().unwrap() {
1323 StreamCommand::Chmod { path, mode } => {
1324 assert_eq!(path, "file.sh");
1325 assert_eq!(mode, 0o755);
1326 }
1327 other => panic!("expected Chmod, got {other:?}"),
1328 }
1329 }
1330
1331 #[test]
1332 fn chown_command() {
1333 let mut payload = Vec::new();
1334 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "owned"));
1335 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_UID, 1000));
1336 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_GID, 1000));
1337 let mut stream = v1_header();
1338 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CHOWN, &payload));
1339 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1340 match reader.next_command().unwrap().unwrap() {
1341 StreamCommand::Chown { path, uid, gid } => {
1342 assert_eq!(path, "owned");
1343 assert_eq!(uid, 1000);
1344 assert_eq!(gid, 1000);
1345 }
1346 other => panic!("expected Chown, got {other:?}"),
1347 }
1348 }
1349
1350 #[test]
1351 fn truncate_command() {
1352 let mut payload = Vec::new();
1353 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "shrink.bin"));
1354 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_SIZE, 4096));
1355 let mut stream = v1_header();
1356 stream
1357 .extend_from_slice(&build_command(BTRFS_SEND_C_TRUNCATE, &payload));
1358 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1359 match reader.next_command().unwrap().unwrap() {
1360 StreamCommand::Truncate { path, size } => {
1361 assert_eq!(path, "shrink.bin");
1362 assert_eq!(size, 4096);
1363 }
1364 other => panic!("expected Truncate, got {other:?}"),
1365 }
1366 }
1367
1368 #[test]
1369 fn write_command() {
1370 let file_data = b"hello world";
1371 let mut payload = Vec::new();
1372 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "data.bin"));
1373 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 512));
1374 payload.extend_from_slice(&tlv(BTRFS_SEND_A_DATA, file_data));
1375 let mut stream = v1_header();
1376 stream.extend_from_slice(&build_command(BTRFS_SEND_C_WRITE, &payload));
1377 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1378 match reader.next_command().unwrap().unwrap() {
1379 StreamCommand::Write { path, offset, data } => {
1380 assert_eq!(path, "data.bin");
1381 assert_eq!(offset, 512);
1382 assert_eq!(data, file_data);
1383 }
1384 other => panic!("expected Write, got {other:?}"),
1385 }
1386 }
1387
1388 #[test]
1389 fn set_xattr_command() {
1390 let xattr_value = b"\x01\x02\x03";
1391 let mut payload = Vec::new();
1392 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file"));
1393 payload.extend_from_slice(&tlv_string(
1394 BTRFS_SEND_A_XATTR_NAME,
1395 "user.test",
1396 ));
1397 payload.extend_from_slice(&tlv(BTRFS_SEND_A_XATTR_DATA, xattr_value));
1398 let mut stream = v1_header();
1399 stream.extend_from_slice(&build_command(
1400 BTRFS_SEND_C_SET_XATTR,
1401 &payload,
1402 ));
1403 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1404 match reader.next_command().unwrap().unwrap() {
1405 StreamCommand::SetXattr { path, name, data } => {
1406 assert_eq!(path, "file");
1407 assert_eq!(name, "user.test");
1408 assert_eq!(data, xattr_value);
1409 }
1410 other => panic!("expected SetXattr, got {other:?}"),
1411 }
1412 }
1413
1414 #[test]
1415 fn utimes_command() {
1416 let mut payload = Vec::new();
1417 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "timed"));
1418 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_ATIME, 1000, 111));
1419 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_MTIME, 2000, 222));
1420 payload.extend_from_slice(&tlv_timespec(BTRFS_SEND_A_CTIME, 3000, 333));
1421 let mut stream = v1_header();
1422 stream.extend_from_slice(&build_command(BTRFS_SEND_C_UTIMES, &payload));
1423 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1424 match reader.next_command().unwrap().unwrap() {
1425 StreamCommand::Utimes {
1426 path,
1427 atime,
1428 mtime,
1429 ctime,
1430 } => {
1431 assert_eq!(path, "timed");
1432 assert_eq!(atime.sec, 1000);
1433 assert_eq!(atime.nsec, 111);
1434 assert_eq!(mtime.sec, 2000);
1435 assert_eq!(mtime.nsec, 222);
1436 assert_eq!(ctime.sec, 3000);
1437 assert_eq!(ctime.nsec, 333);
1438 }
1439 other => panic!("expected Utimes, got {other:?}"),
1440 }
1441 }
1442
1443 #[test]
1446 fn subvol_command() {
1447 let uuid =
1448 Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
1449 let mut payload = Vec::new();
1450 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "my_subvol"));
1451 payload.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1452 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 42));
1453 let mut stream = v1_header();
1454 stream.extend_from_slice(&build_command(BTRFS_SEND_C_SUBVOL, &payload));
1455 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1456 match reader.next_command().unwrap().unwrap() {
1457 StreamCommand::Subvol {
1458 path,
1459 uuid: u,
1460 ctransid,
1461 } => {
1462 assert_eq!(path, "my_subvol");
1463 assert_eq!(u, uuid);
1464 assert_eq!(ctransid, 42);
1465 }
1466 other => panic!("expected Subvol, got {other:?}"),
1467 }
1468 }
1469
1470 #[test]
1471 fn snapshot_command() {
1472 let uuid =
1473 Uuid::parse_str("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee").unwrap();
1474 let clone_uuid =
1475 Uuid::parse_str("11111111-2222-3333-4444-555555555555").unwrap();
1476 let mut payload = Vec::new();
1477 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "my_snap"));
1478 payload.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1479 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 100));
1480 payload
1481 .extend_from_slice(&tlv_uuid(BTRFS_SEND_A_CLONE_UUID, clone_uuid));
1482 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_CTRANSID, 99));
1483 let mut stream = v1_header();
1484 stream
1485 .extend_from_slice(&build_command(BTRFS_SEND_C_SNAPSHOT, &payload));
1486 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1487 match reader.next_command().unwrap().unwrap() {
1488 StreamCommand::Snapshot {
1489 path,
1490 uuid: u,
1491 ctransid,
1492 clone_uuid: cu,
1493 clone_ctransid,
1494 } => {
1495 assert_eq!(path, "my_snap");
1496 assert_eq!(u, uuid);
1497 assert_eq!(ctransid, 100);
1498 assert_eq!(cu, clone_uuid);
1499 assert_eq!(clone_ctransid, 99);
1500 }
1501 other => panic!("expected Snapshot, got {other:?}"),
1502 }
1503 }
1504
1505 #[test]
1508 fn clone_command() {
1509 let clone_uuid =
1510 Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap();
1511 let mut payload = Vec::new();
1512 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "dest"));
1513 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 0));
1514 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_LEN, 4096));
1515 payload
1516 .extend_from_slice(&tlv_uuid(BTRFS_SEND_A_CLONE_UUID, clone_uuid));
1517 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_CTRANSID, 7));
1518 payload
1519 .extend_from_slice(&tlv_string(BTRFS_SEND_A_CLONE_PATH, "source"));
1520 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CLONE_OFFSET, 8192));
1521 let mut stream = v1_header();
1522 stream.extend_from_slice(&build_command(BTRFS_SEND_C_CLONE, &payload));
1523 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1524 match reader.next_command().unwrap().unwrap() {
1525 StreamCommand::Clone {
1526 path,
1527 offset,
1528 len,
1529 clone_uuid: cu,
1530 clone_ctransid,
1531 clone_path,
1532 clone_offset,
1533 } => {
1534 assert_eq!(path, "dest");
1535 assert_eq!(offset, 0);
1536 assert_eq!(len, 4096);
1537 assert_eq!(cu, clone_uuid);
1538 assert_eq!(clone_ctransid, 7);
1539 assert_eq!(clone_path, "source");
1540 assert_eq!(clone_offset, 8192);
1541 }
1542 other => panic!("expected Clone, got {other:?}"),
1543 }
1544 }
1545
1546 #[test]
1549 fn multiple_commands_in_sequence() {
1550 let uuid = Uuid::nil();
1551 let mut stream = v1_header();
1552
1553 let mut p = Vec::new();
1555 p.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "vol"));
1556 p.extend_from_slice(&tlv_uuid(BTRFS_SEND_A_UUID, uuid));
1557 p.extend_from_slice(&tlv_u64(BTRFS_SEND_A_CTRANSID, 1));
1558 stream.extend_from_slice(&build_command(BTRFS_SEND_C_SUBVOL, &p));
1559
1560 stream.extend_from_slice(&build_command(
1562 BTRFS_SEND_C_MKFILE,
1563 &tlv_string(BTRFS_SEND_A_PATH, "file.txt"),
1564 ));
1565
1566 stream.extend_from_slice(&build_command(BTRFS_SEND_C_END, &[]));
1568
1569 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1570
1571 assert!(matches!(
1572 reader.next_command().unwrap().unwrap(),
1573 StreamCommand::Subvol { .. }
1574 ));
1575 assert!(matches!(
1576 reader.next_command().unwrap().unwrap(),
1577 StreamCommand::Mkfile { .. }
1578 ));
1579 assert!(matches!(
1580 reader.next_command().unwrap().unwrap(),
1581 StreamCommand::End
1582 ));
1583 assert!(reader.next_command().unwrap().is_none());
1584 }
1585
1586 #[test]
1589 fn unknown_command_type() {
1590 let mut stream = v1_header();
1591 stream.extend_from_slice(&build_command(99, &[]));
1592 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1593 let err = reader.next_command().unwrap_err();
1594 assert!(
1595 format!("{err}").contains("unknown send stream command type 99"),
1596 "got: {err}"
1597 );
1598 }
1599
1600 #[test]
1601 fn missing_required_attribute() {
1602 let mut stream = v1_header();
1604 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFILE, &[]));
1605 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1606 let err = reader.next_command().unwrap_err();
1607 assert!(
1608 format!("{err}").contains("missing required attribute"),
1609 "got: {err}"
1610 );
1611 }
1612
1613 #[test]
1614 fn truncated_payload() {
1615 let mut stream = v1_header();
1616 let len = 100u32;
1619 let cmd = BTRFS_SEND_C_END;
1620 let mut crc_input = Vec::new();
1621 crc_input.extend_from_slice(&len.to_le_bytes());
1622 crc_input.extend_from_slice(&cmd.to_le_bytes());
1623 crc_input.extend_from_slice(&[0u8; 4]);
1624 let crc = crc32c::crc32c(&crc_input);
1625 stream.extend_from_slice(&len.to_le_bytes());
1626 stream.extend_from_slice(&cmd.to_le_bytes());
1627 stream.extend_from_slice(&crc.to_le_bytes());
1628 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1630 let err = reader.next_command().unwrap_err();
1631 assert!(format!("{err}").contains("truncated"), "got: {err}");
1632 }
1633
1634 #[test]
1635 fn into_inner_returns_reader() {
1636 let stream = v1_header();
1637 let cursor = Cursor::new(stream);
1638 let reader = StreamReader::new(cursor).unwrap();
1639 let _inner: Cursor<Vec<u8>> = reader.into_inner();
1640 }
1641
1642 #[test]
1645 fn update_extent_command() {
1646 let mut payload = Vec::new();
1647 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "extent.dat"));
1648 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_FILE_OFFSET, 65536));
1649 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_SIZE, 131072));
1650 let mut stream = v1_header();
1651 stream.extend_from_slice(&build_command(
1652 BTRFS_SEND_C_UPDATE_EXTENT,
1653 &payload,
1654 ));
1655 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1656 match reader.next_command().unwrap().unwrap() {
1657 StreamCommand::UpdateExtent { path, offset, len } => {
1658 assert_eq!(path, "extent.dat");
1659 assert_eq!(offset, 65536);
1660 assert_eq!(len, 131072);
1661 }
1662 other => panic!("expected UpdateExtent, got {other:?}"),
1663 }
1664 }
1665
1666 #[test]
1669 fn mknod_command() {
1670 let mut payload = Vec::new();
1671 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "dev_node"));
1672 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_MODE, 0o660));
1673 payload.extend_from_slice(&tlv_u64(BTRFS_SEND_A_RDEV, 0x0801)); let mut stream = v1_header();
1675 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKNOD, &payload));
1676 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1677 match reader.next_command().unwrap().unwrap() {
1678 StreamCommand::Mknod { path, mode, rdev } => {
1679 assert_eq!(path, "dev_node");
1680 assert_eq!(mode, 0o660);
1681 assert_eq!(rdev, 0x0801);
1682 }
1683 other => panic!("expected Mknod, got {other:?}"),
1684 }
1685 }
1686
1687 #[test]
1688 fn mkfifo_command() {
1689 let payload = tlv_string(BTRFS_SEND_A_PATH, "my_fifo");
1690 let mut stream = v1_header();
1691 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKFIFO, &payload));
1692 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1693 match reader.next_command().unwrap().unwrap() {
1694 StreamCommand::Mkfifo { path } => assert_eq!(path, "my_fifo"),
1695 other => panic!("expected Mkfifo, got {other:?}"),
1696 }
1697 }
1698
1699 #[test]
1700 fn mksock_command() {
1701 let payload = tlv_string(BTRFS_SEND_A_PATH, "my_sock");
1702 let mut stream = v1_header();
1703 stream.extend_from_slice(&build_command(BTRFS_SEND_C_MKSOCK, &payload));
1704 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1705 match reader.next_command().unwrap().unwrap() {
1706 StreamCommand::Mksock { path } => assert_eq!(path, "my_sock"),
1707 other => panic!("expected Mksock, got {other:?}"),
1708 }
1709 }
1710
1711 #[test]
1714 fn link_command() {
1715 let mut payload = Vec::new();
1716 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "hardlink"));
1717 payload
1718 .extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH_LINK, "original"));
1719 let mut stream = v1_header();
1720 stream.extend_from_slice(&build_command(BTRFS_SEND_C_LINK, &payload));
1721 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1722 match reader.next_command().unwrap().unwrap() {
1723 StreamCommand::Link { path, target } => {
1724 assert_eq!(path, "hardlink");
1725 assert_eq!(target, "original");
1726 }
1727 other => panic!("expected Link, got {other:?}"),
1728 }
1729 }
1730
1731 #[test]
1732 fn remove_xattr_command() {
1733 let mut payload = Vec::new();
1734 payload.extend_from_slice(&tlv_string(BTRFS_SEND_A_PATH, "file"));
1735 payload.extend_from_slice(&tlv_string(
1736 BTRFS_SEND_A_XATTR_NAME,
1737 "user.old",
1738 ));
1739 let mut stream = v1_header();
1740 stream.extend_from_slice(&build_command(
1741 BTRFS_SEND_C_REMOVE_XATTR,
1742 &payload,
1743 ));
1744 let mut reader = StreamReader::new(Cursor::new(stream)).unwrap();
1745 match reader.next_command().unwrap().unwrap() {
1746 StreamCommand::RemoveXattr { path, name } => {
1747 assert_eq!(path, "file");
1748 assert_eq!(name, "user.old");
1749 }
1750 other => panic!("expected RemoveXattr, got {other:?}"),
1751 }
1752 }
1753}