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