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