1#[cfg(feature = "std")]
11use super::io::{self, Read, Writable, Write};
12use super::susp::SystemUseHeader;
13use crate::types::U32LsbMsb;
14
15#[cfg(feature = "alloc")]
16bitflags::bitflags! {
17 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19 pub struct PosixFileMode: u32 {
20 const S_IFSOCK = 0o140000;
23 const S_IFLNK = 0o120000;
25 const S_IFREG = 0o100000;
27 const S_IFBLK = 0o060000;
29 const S_IFDIR = 0o040000;
31 const S_IFCHR = 0o020000;
33 const S_IFIFO = 0o010000;
35
36 const S_ISUID = 0o4000;
39 const S_ISGID = 0o2000;
41 const S_ISVTX = 0o1000;
43
44 const S_IRUSR = 0o0400;
46 const S_IWUSR = 0o0200;
48 const S_IXUSR = 0o0100;
50
51 const S_IRGRP = 0o0040;
53 const S_IWGRP = 0o0020;
55 const S_IXGRP = 0o0010;
57
58 const S_IROTH = 0o0004;
60 const S_IWOTH = 0o0002;
62 const S_IXOTH = 0o0001;
64 }
65}
66
67#[cfg(feature = "alloc")]
68impl PosixFileMode {
69 pub fn regular(perms: u32) -> Self {
71 Self::S_IFREG | Self::from_bits_truncate(perms & 0o7777)
72 }
73
74 pub fn directory(perms: u32) -> Self {
76 Self::S_IFDIR | Self::from_bits_truncate(perms & 0o7777)
77 }
78
79 pub fn symlink() -> Self {
81 Self::S_IFLNK | Self::from_bits_truncate(0o777)
82 }
83
84 pub fn default_file() -> Self {
86 Self::regular(0o644)
87 }
88
89 pub fn default_dir() -> Self {
91 Self::directory(0o755)
92 }
93}
94
95#[repr(C)]
99#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
100pub struct PxEntry {
101 pub file_mode: U32LsbMsb,
103 pub file_links: U32LsbMsb,
105 pub file_uid: U32LsbMsb,
107 pub file_gid: U32LsbMsb,
109 pub file_serial: U32LsbMsb,
111}
112
113impl PxEntry {
114 pub fn new(mode: u32, nlink: u32, uid: u32, gid: u32, serial: u32) -> Self {
116 Self {
117 file_mode: U32LsbMsb::new(mode),
118 file_links: U32LsbMsb::new(nlink),
119 file_uid: U32LsbMsb::new(uid),
120 file_gid: U32LsbMsb::new(gid),
121 file_serial: U32LsbMsb::new(serial),
122 }
123 }
124}
125
126#[cfg(feature = "std")]
127impl PxEntry {
128 pub fn header(&self) -> SystemUseHeader {
130 SystemUseHeader {
131 sig: *b"PX",
132 length: 44,
133 version: 1,
134 }
135 }
136}
137
138#[repr(C)]
142#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
143pub struct PnEntry {
144 pub dev_high: U32LsbMsb,
146 pub dev_low: U32LsbMsb,
148}
149
150impl PnEntry {
151 pub fn new(major: u32, minor: u32) -> Self {
153 Self {
154 dev_high: U32LsbMsb::new(major),
155 dev_low: U32LsbMsb::new(minor),
156 }
157 }
158
159 pub fn dev(&self) -> u64 {
161 ((self.dev_high.read() as u64) << 32) | (self.dev_low.read() as u64)
162 }
163}
164
165#[cfg(feature = "std")]
166impl PnEntry {
167 pub fn header(&self) -> SystemUseHeader {
169 SystemUseHeader {
170 sig: *b"PN",
171 length: 20,
172 version: 1,
173 }
174 }
175}
176
177#[cfg(feature = "alloc")]
178bitflags::bitflags! {
179 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
181 pub struct NmFlags: u8 {
182 const CONTINUE = 0x01;
184 const CURRENT = 0x02;
186 const PARENT = 0x04;
188 const RESERVED3 = 0x08;
190 const RESERVED4 = 0x10;
192 const HOST = 0x20;
194 }
195}
196
197#[derive(Debug, Clone)]
201#[cfg(feature = "alloc")]
202pub struct NmEntry {
203 pub flags: NmFlags,
205 pub name: alloc::vec::Vec<u8>,
207}
208
209#[cfg(feature = "alloc")]
210impl NmEntry {
211 pub fn new(name: &[u8]) -> Self {
213 Self {
214 flags: NmFlags::empty(),
215 name: name.to_vec(),
216 }
217 }
218
219 pub fn current() -> Self {
221 Self {
222 flags: NmFlags::CURRENT,
223 name: alloc::vec::Vec::new(),
224 }
225 }
226
227 pub fn parent() -> Self {
229 Self {
230 flags: NmFlags::PARENT,
231 name: alloc::vec::Vec::new(),
232 }
233 }
234
235 pub fn size(&self) -> usize {
237 5 + self.name.len()
238 }
239}
240
241#[cfg(feature = "std")]
242impl NmEntry {
243 pub fn header(&self) -> SystemUseHeader {
245 SystemUseHeader {
246 sig: *b"NM",
247 length: self.size() as u8,
248 version: 1,
249 }
250 }
251}
252
253#[cfg(feature = "alloc")]
254bitflags::bitflags! {
255 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
257 pub struct SlComponentFlags: u8 {
258 const CONTINUE = 0x01;
260 const CURRENT = 0x02;
262 const PARENT = 0x04;
264 const ROOT = 0x08;
266 const RESERVED4 = 0x10;
268 const VOLROOT = 0x20;
270 }
271}
272
273#[derive(Debug, Clone)]
275#[cfg(feature = "alloc")]
276pub struct SlComponent {
277 pub flags: SlComponentFlags,
279 pub content: alloc::vec::Vec<u8>,
281}
282
283#[cfg(feature = "alloc")]
284impl SlComponent {
285 pub fn new(segment: &[u8]) -> Self {
287 Self {
288 flags: SlComponentFlags::empty(),
289 content: segment.to_vec(),
290 }
291 }
292
293 pub fn root() -> Self {
295 Self {
296 flags: SlComponentFlags::ROOT,
297 content: alloc::vec::Vec::new(),
298 }
299 }
300
301 pub fn current() -> Self {
303 Self {
304 flags: SlComponentFlags::CURRENT,
305 content: alloc::vec::Vec::new(),
306 }
307 }
308
309 pub fn parent() -> Self {
311 Self {
312 flags: SlComponentFlags::PARENT,
313 content: alloc::vec::Vec::new(),
314 }
315 }
316
317 pub fn size(&self) -> usize {
319 2 + self.content.len()
320 }
321}
322
323#[derive(Debug, Clone)]
325#[cfg(feature = "alloc")]
326pub struct SlEntry {
327 pub flags: u8,
329 pub components: alloc::vec::Vec<SlComponent>,
331}
332
333#[cfg(feature = "alloc")]
334impl SlEntry {
335 pub fn from_path(target: &str) -> Self {
337 let mut components = alloc::vec::Vec::new();
338
339 for component in hadris_path::VPath::new(target).components() {
340 match component {
341 hadris_path::Component::Root => components.push(SlComponent::root()),
342 hadris_path::Component::Current => components.push(SlComponent::current()),
343 hadris_path::Component::Parent => components.push(SlComponent::parent()),
344 hadris_path::Component::Normal(segment) => {
345 components.push(SlComponent::new(segment.as_bytes()));
346 }
347 }
348 }
349
350 Self {
351 flags: 0,
352 components,
353 }
354 }
355
356 pub fn size(&self) -> usize {
358 5 + self.components.iter().map(|c| c.size()).sum::<usize>()
359 }
360}
361
362#[cfg(feature = "std")]
363impl SlEntry {
364 pub fn header(&self) -> SystemUseHeader {
366 SystemUseHeader {
367 sig: *b"SL",
368 length: self.size() as u8,
369 version: 1,
370 }
371 }
372}
373
374#[cfg(feature = "alloc")]
375bitflags::bitflags! {
376 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
378 pub struct TfFlags: u8 {
379 const CREATION = 0x01;
381 const MODIFY = 0x02;
383 const ACCESS = 0x04;
385 const ATTRIBUTES = 0x08;
387 const BACKUP = 0x10;
389 const EXPIRATION = 0x20;
391 const EFFECTIVE = 0x40;
393 const LONG_FORM = 0x80;
395 }
396}
397
398#[derive(Debug, Clone)]
400#[cfg(feature = "alloc")]
401pub struct TfEntry {
402 pub flags: TfFlags,
404 pub timestamps: alloc::vec::Vec<u8>,
407}
408
409#[cfg(feature = "alloc")]
410impl TfEntry {
411 pub fn new_short(mtime: &[u8; 7], atime: &[u8; 7]) -> Self {
415 let flags = TfFlags::MODIFY | TfFlags::ACCESS;
416 let mut timestamps = alloc::vec::Vec::with_capacity(14);
417 timestamps.extend_from_slice(mtime);
418 timestamps.extend_from_slice(atime);
419 Self { flags, timestamps }
420 }
421
422 pub fn size(&self) -> usize {
424 5 + self.timestamps.len()
425 }
426}
427
428#[cfg(feature = "std")]
429impl TfEntry {
430 pub fn header(&self) -> SystemUseHeader {
432 SystemUseHeader {
433 sig: *b"TF",
434 length: self.size() as u8,
435 version: 1,
436 }
437 }
438}
439
440#[repr(C)]
442#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
443pub struct ClEntry {
444 pub child_directory_location: U32LsbMsb,
446}
447
448impl ClEntry {
449 pub fn new(location: u32) -> Self {
451 Self {
452 child_directory_location: U32LsbMsb::new(location),
453 }
454 }
455}
456
457#[cfg(feature = "std")]
458impl ClEntry {
459 pub fn header(&self) -> SystemUseHeader {
461 SystemUseHeader {
462 sig: *b"CL",
463 length: 12,
464 version: 1,
465 }
466 }
467}
468
469#[repr(C)]
471#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
472pub struct PlEntry {
473 pub parent_directory_location: U32LsbMsb,
475}
476
477impl PlEntry {
478 pub fn new(location: u32) -> Self {
480 Self {
481 parent_directory_location: U32LsbMsb::new(location),
482 }
483 }
484}
485
486#[cfg(feature = "std")]
487impl PlEntry {
488 pub fn header(&self) -> SystemUseHeader {
490 SystemUseHeader {
491 sig: *b"PL",
492 length: 12,
493 version: 1,
494 }
495 }
496}
497
498#[derive(Debug, Clone, Copy)]
502pub struct ReEntry;
503
504#[cfg(feature = "std")]
505impl ReEntry {
506 pub fn header(&self) -> SystemUseHeader {
508 SystemUseHeader {
509 sig: *b"RE",
510 length: 4,
511 version: 1,
512 }
513 }
514}
515
516io_transform! {
517#[cfg(feature = "std")]
518impl Writable for PxEntry {
519 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
520 self.header().write(writer).await?;
521 writer.write_all(bytemuck::bytes_of(self)).await?;
522 Ok(())
523 }
524}
525
526#[cfg(feature = "std")]
527impl PxEntry {
528 pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
530 let mut buf = [0u8; 40];
531 let len = (header.length as usize).saturating_sub(4).min(40);
532 data.read_exact(&mut buf[..len]).await?;
533
534 Ok(Self {
535 file_mode: *bytemuck::from_bytes(&buf[0..8]),
536 file_links: *bytemuck::from_bytes(&buf[8..16]),
537 file_uid: *bytemuck::from_bytes(&buf[16..24]),
538 file_gid: *bytemuck::from_bytes(&buf[24..32]),
539 file_serial: if len >= 40 {
540 *bytemuck::from_bytes(&buf[32..40])
541 } else {
542 U32LsbMsb::new(0)
543 },
544 })
545 }
546}
547
548#[cfg(feature = "std")]
549impl Writable for PnEntry {
550 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
551 self.header().write(writer).await?;
552 writer.write_all(bytemuck::bytes_of(self)).await?;
553 Ok(())
554 }
555}
556
557#[cfg(feature = "std")]
558impl PnEntry {
559 pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
561 let mut buf = [0u8; 16];
562 data.read_exact(&mut buf).await?;
563 Ok(Self {
564 dev_high: *bytemuck::from_bytes(&buf[0..8]),
565 dev_low: *bytemuck::from_bytes(&buf[8..16]),
566 })
567 }
568}
569
570#[cfg(feature = "std")]
571impl Writable for NmEntry {
572 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
573 self.header().write(writer).await?;
574 writer.write_all(&[self.flags.bits()]).await?;
575 writer.write_all(&self.name).await?;
576 Ok(())
577 }
578}
579
580#[cfg(feature = "std")]
581impl NmEntry {
582 pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
584 let mut flags_byte = [0u8; 1];
585 data.read_exact(&mut flags_byte).await?;
586 let name_len = (header.length as usize).saturating_sub(5);
587 let mut name = alloc::vec![0u8; name_len];
588 data.read_exact(&mut name).await?;
589 Ok(Self {
590 flags: NmFlags::from_bits_truncate(flags_byte[0]),
591 name,
592 })
593 }
594}
595
596#[cfg(feature = "std")]
597impl Writable for SlEntry {
598 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
599 self.header().write(writer).await?;
600 writer.write_all(&[self.flags]).await?;
601 for component in &self.components {
602 writer.write_all(&[component.flags.bits(), component.content.len() as u8]).await?;
603 writer.write_all(&component.content).await?;
604 }
605 Ok(())
606 }
607}
608
609#[cfg(feature = "std")]
610impl SlEntry {
611 pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
613 let mut flags = [0u8; 1];
614 data.read_exact(&mut flags).await?;
615
616 let mut remaining = (header.length as usize).saturating_sub(5);
617 let mut components = alloc::vec::Vec::new();
618
619 while remaining >= 2 {
620 let mut comp_header = [0u8; 2];
621 data.read_exact(&mut comp_header).await?;
622 remaining -= 2;
623
624 let comp_flags = SlComponentFlags::from_bits_truncate(comp_header[0]);
625 let comp_len = comp_header[1] as usize;
626
627 let mut content = alloc::vec![0u8; comp_len.min(remaining)];
628 data.read_exact(&mut content).await?;
629 remaining -= content.len();
630
631 components.push(SlComponent {
632 flags: comp_flags,
633 content,
634 });
635 }
636
637 Ok(Self {
638 flags: flags[0],
639 components,
640 })
641 }
642}
643
644#[cfg(feature = "std")]
645impl Writable for TfEntry {
646 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
647 self.header().write(writer).await?;
648 writer.write_all(&[self.flags.bits()]).await?;
649 writer.write_all(&self.timestamps).await?;
650 Ok(())
651 }
652}
653
654#[cfg(feature = "std")]
655impl TfEntry {
656 pub async fn parse_data<R: Read>(header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
658 let mut flags_byte = [0u8; 1];
659 data.read_exact(&mut flags_byte).await?;
660 let flags = TfFlags::from_bits_truncate(flags_byte[0]);
661
662 let ts_len = (header.length as usize).saturating_sub(5);
663 let mut timestamps = alloc::vec![0u8; ts_len];
664 data.read_exact(&mut timestamps).await?;
665
666 Ok(Self { flags, timestamps })
667 }
668}
669
670#[cfg(feature = "std")]
671impl Writable for ClEntry {
672 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
673 self.header().write(writer).await?;
674 writer.write_all(bytemuck::bytes_of(&self.child_directory_location)).await?;
675 Ok(())
676 }
677}
678
679#[cfg(feature = "std")]
680impl ClEntry {
681 pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
683 let mut buf = [0u8; 8];
684 data.read_exact(&mut buf).await?;
685 Ok(Self {
686 child_directory_location: *bytemuck::from_bytes(&buf),
687 })
688 }
689}
690
691#[cfg(feature = "std")]
692impl Writable for PlEntry {
693 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
694 self.header().write(writer).await?;
695 writer.write_all(bytemuck::bytes_of(&self.parent_directory_location)).await?;
696 Ok(())
697 }
698}
699
700#[cfg(feature = "std")]
701impl PlEntry {
702 pub async fn parse_data<R: Read>(_header: SystemUseHeader, data: &mut R) -> io::Result<Self> {
704 let mut buf = [0u8; 8];
705 data.read_exact(&mut buf).await?;
706 Ok(Self {
707 parent_directory_location: *bytemuck::from_bytes(&buf),
708 })
709 }
710}
711
712#[cfg(feature = "std")]
713impl Writable for ReEntry {
714 async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
715 self.header().write(writer).await
716 }
717}
718} #[derive(Debug, Clone, Copy)]
722#[cfg(feature = "alloc")]
723pub struct RripOptions {
724 pub enabled: bool,
726 pub relocate_deep_dirs: bool,
728 pub preserve_permissions: bool,
730 pub preserve_ownership: bool,
732 pub preserve_timestamps: bool,
734 pub preserve_symlinks: bool,
736 pub preserve_devices: bool,
738}
739
740#[cfg(feature = "alloc")]
741impl Default for RripOptions {
742 fn default() -> Self {
743 Self {
744 enabled: true,
745 relocate_deep_dirs: true,
746 preserve_permissions: true,
747 preserve_ownership: true,
748 preserve_timestamps: true,
749 preserve_symlinks: true,
750 preserve_devices: true,
751 }
752 }
753}
754
755#[cfg(feature = "alloc")]
756impl RripOptions {
757 pub fn disabled() -> Self {
759 Self {
760 enabled: false,
761 relocate_deep_dirs: false,
762 preserve_permissions: false,
763 preserve_ownership: false,
764 preserve_timestamps: false,
765 preserve_symlinks: false,
766 preserve_devices: false,
767 }
768 }
769}
770
771#[cfg(feature = "alloc")]
773pub struct RripBuilder {
774 builder: super::susp::SystemUseBuilder,
775}
776
777#[cfg(feature = "alloc")]
778impl RripBuilder {
779 pub fn new() -> Self {
781 Self {
782 builder: super::susp::SystemUseBuilder::new(),
783 }
784 }
785
786 pub fn add_sp(&mut self, bytes_skipped: u8) -> &mut Self {
788 self.builder.add_sp(bytes_skipped);
789 self
790 }
791
792 pub fn add_rrip_er(&mut self) -> &mut Self {
794 self.builder.add_er(
796 "RRIP_1991A",
797 "THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS",
798 "PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR CONTACT INFORMATION.",
799 1,
800 );
801 self
802 }
803
804 pub fn add_px(&mut self, mode: u32, nlink: u32, uid: u32, gid: u32, serial: u32) -> &mut Self {
806 let px = PxEntry::new(mode, nlink, uid, gid, serial);
807 let mut buf = alloc::vec![0u8; 44];
808 buf[0..2].copy_from_slice(b"PX");
809 buf[2] = 44;
810 buf[3] = 1;
811 buf[4..44].copy_from_slice(bytemuck::bytes_of(&px));
812 self.builder.add_raw(buf);
813 self
814 }
815
816 pub fn add_pn(&mut self, major: u32, minor: u32) -> &mut Self {
818 let pn = PnEntry::new(major, minor);
819 let mut buf = alloc::vec![0u8; 20];
820 buf[0..2].copy_from_slice(b"PN");
821 buf[2] = 20;
822 buf[3] = 1;
823 buf[4..20].copy_from_slice(bytemuck::bytes_of(&pn));
824 self.builder.add_raw(buf);
825 self
826 }
827
828 pub fn add_nm(&mut self, name: &[u8]) -> &mut Self {
830 let mut remaining = name;
832 let max_per_entry = 250; while !remaining.is_empty() {
835 let chunk_len = remaining.len().min(max_per_entry);
836 let (chunk, rest) = remaining.split_at(chunk_len);
837 remaining = rest;
838
839 let flags = if remaining.is_empty() { 0 } else { 0x01 }; let total_len = 5 + chunk.len();
841 let mut buf = alloc::vec![0u8; total_len];
842 buf[0..2].copy_from_slice(b"NM");
843 buf[2] = total_len as u8;
844 buf[3] = 1;
845 buf[4] = flags;
846 buf[5..].copy_from_slice(chunk);
847 self.builder.add_raw(buf);
848 }
849 self
850 }
851
852 pub fn add_nm_current(&mut self) -> &mut Self {
854 let buf = alloc::vec![b'N', b'M', 5, 1, 0x02]; self.builder.add_raw(buf);
856 self
857 }
858
859 pub fn add_nm_parent(&mut self) -> &mut Self {
861 let buf = alloc::vec![b'N', b'M', 5, 1, 0x04]; self.builder.add_raw(buf);
863 self
864 }
865
866 pub fn add_sl(&mut self, target: &str) -> &mut Self {
868 let sl = SlEntry::from_path(target);
869 let size = sl.size();
870 let mut buf = alloc::vec![0u8; size];
871 buf[0..2].copy_from_slice(b"SL");
872 buf[2] = size as u8;
873 buf[3] = 1;
874 buf[4] = sl.flags;
875
876 let mut offset = 5;
877 for component in &sl.components {
878 buf[offset] = component.flags.bits();
879 buf[offset + 1] = component.content.len() as u8;
880 buf[offset + 2..offset + 2 + component.content.len()]
881 .copy_from_slice(&component.content);
882 offset += 2 + component.content.len();
883 }
884
885 self.builder.add_raw(buf);
886 self
887 }
888
889 pub fn add_tf_short(&mut self, mtime: &[u8; 7], atime: &[u8; 7]) -> &mut Self {
891 self.add_tf(None, mtime, atime)
892 }
893
894 pub fn add_tf(
900 &mut self,
901 creation: Option<&[u8; 7]>,
902 mtime: &[u8; 7],
903 atime: &[u8; 7],
904 ) -> &mut Self {
905 let mut flags = 0x06u8; let mut len = 5 + 7 + 7;
908 if creation.is_some() {
909 flags |= 0x01; len += 7;
911 }
912 let mut buf = alloc::vec![0u8; len];
913 buf[0..2].copy_from_slice(b"TF");
914 buf[2] = len as u8;
915 buf[3] = 1;
916 buf[4] = flags;
917 let mut off = 5;
918 if let Some(ctime) = creation {
919 buf[off..off + 7].copy_from_slice(ctime);
920 off += 7;
921 }
922 buf[off..off + 7].copy_from_slice(mtime);
923 buf[off + 7..off + 14].copy_from_slice(atime);
924 self.builder.add_raw(buf);
925 self
926 }
927
928 pub fn add_cl(&mut self, child_location: u32) -> &mut Self {
930 let cl = ClEntry::new(child_location);
931 let mut buf = alloc::vec![0u8; 12];
932 buf[0..2].copy_from_slice(b"CL");
933 buf[2] = 12;
934 buf[3] = 1;
935 buf[4..12].copy_from_slice(bytemuck::bytes_of(&cl.child_directory_location));
936 self.builder.add_raw(buf);
937 self
938 }
939
940 pub fn add_pl(&mut self, parent_location: u32) -> &mut Self {
942 let pl = PlEntry::new(parent_location);
943 let mut buf = alloc::vec![0u8; 12];
944 buf[0..2].copy_from_slice(b"PL");
945 buf[2] = 12;
946 buf[3] = 1;
947 buf[4..12].copy_from_slice(bytemuck::bytes_of(&pl.parent_directory_location));
948 self.builder.add_raw(buf);
949 self
950 }
951
952 pub fn add_re(&mut self) -> &mut Self {
954 let buf = alloc::vec![b'R', b'E', 4, 1];
955 self.builder.add_raw(buf);
956 self
957 }
958
959 pub fn add_ce(&mut self, ce: super::susp::ContinuationArea) -> &mut Self {
961 self.builder.add_ce(ce);
962 self
963 }
964
965 pub fn add_st(&mut self) -> &mut Self {
967 self.builder.add_st();
968 self
969 }
970
971 pub fn size(&self) -> usize {
973 self.builder.size()
974 }
975
976 pub fn build(&self) -> alloc::vec::Vec<u8> {
978 self.builder.build()
979 }
980
981 pub fn build_split(&self, max_inline: usize) -> super::susp::SplitSu {
985 self.builder.build_split(max_inline)
986 }
987
988 pub fn is_empty(&self) -> bool {
990 self.builder.is_empty()
991 }
992}
993
994#[cfg(feature = "alloc")]
995impl Default for RripBuilder {
996 fn default() -> Self {
997 Self::new()
998 }
999}
1000
1001#[derive(Debug, Clone)]
1003#[cfg(feature = "alloc")]
1004pub enum RockRidgeEntry {
1005 PosixAttributes(PxEntry),
1007 DeviceNumber(PnEntry),
1009 AlternateName(NmEntry),
1011 SymbolicLink(SlEntry),
1013 Timestamps(TfEntry),
1015 ChildLink(ClEntry),
1017 ParentLink(PlEntry),
1019 Relocated,
1021 Unknown(SystemUseHeader),
1023}
1024
1025sync_only! {
1026#[cfg(all(feature = "std", test))]
1027mod tests {
1028 use super::*;
1029 use alloc::vec;
1030
1031 static_assertions::const_assert_eq!(size_of::<PxEntry>(), 40);
1032 static_assertions::const_assert_eq!(size_of::<PnEntry>(), 16);
1033 static_assertions::const_assert_eq!(size_of::<ClEntry>(), 8);
1034 static_assertions::const_assert_eq!(size_of::<PlEntry>(), 8);
1035
1036 #[test]
1037 fn test_px_entry_new() {
1038 let px = PxEntry::new(0o100644, 1, 1000, 1000, 12345);
1039 assert_eq!(px.file_mode.read(), 0o100644);
1040 assert_eq!(px.file_links.read(), 1);
1041 assert_eq!(px.file_uid.read(), 1000);
1042 assert_eq!(px.file_gid.read(), 1000);
1043 assert_eq!(px.file_serial.read(), 12345);
1044 }
1045
1046 #[test]
1047 fn test_px_entry_header() {
1048 let px = PxEntry::new(0o100644, 1, 0, 0, 0);
1049 let header = px.header();
1050 assert_eq!(&header.sig, b"PX");
1051 assert_eq!(header.length, 44);
1052 assert_eq!(header.version, 1);
1053 }
1054
1055 #[test]
1056 fn test_pn_entry_new() {
1057 let pn = PnEntry::new(8, 1);
1058 assert_eq!(pn.dev_high.read(), 8);
1059 assert_eq!(pn.dev_low.read(), 1);
1060 }
1061
1062 #[test]
1063 fn test_pn_entry_dev() {
1064 let pn = PnEntry::new(0x12345678, 0xABCDEF01);
1065 let dev = pn.dev();
1066 assert_eq!(dev, (0x12345678u64 << 32) | 0xABCDEF01u64);
1067 }
1068
1069 #[test]
1070 fn test_cl_entry_new() {
1071 let cl = ClEntry::new(100);
1072 assert_eq!(cl.child_directory_location.read(), 100);
1073 }
1074
1075 #[test]
1076 fn test_pl_entry_new() {
1077 let pl = PlEntry::new(50);
1078 assert_eq!(pl.parent_directory_location.read(), 50);
1079 }
1080
1081 #[test]
1082 fn test_posix_file_mode_regular() {
1083 let mode = PosixFileMode::regular(0o644);
1084 assert!(mode.contains(PosixFileMode::S_IFREG));
1085 assert!(mode.contains(PosixFileMode::S_IRUSR));
1086 assert!(mode.contains(PosixFileMode::S_IWUSR));
1087 assert!(mode.contains(PosixFileMode::S_IRGRP));
1088 assert!(mode.contains(PosixFileMode::S_IROTH));
1089 assert!(!mode.contains(PosixFileMode::S_IXUSR));
1090 }
1091
1092 #[test]
1093 fn test_posix_file_mode_directory() {
1094 let mode = PosixFileMode::directory(0o755);
1095 assert!(mode.contains(PosixFileMode::S_IFDIR));
1096 assert!(mode.contains(PosixFileMode::S_IRUSR));
1097 assert!(mode.contains(PosixFileMode::S_IWUSR));
1098 assert!(mode.contains(PosixFileMode::S_IXUSR));
1099 assert!(mode.contains(PosixFileMode::S_IRGRP));
1100 assert!(mode.contains(PosixFileMode::S_IXGRP));
1101 }
1102
1103 #[test]
1104 fn test_posix_file_mode_symlink() {
1105 let mode = PosixFileMode::symlink();
1106 assert!(mode.contains(PosixFileMode::S_IFLNK));
1107 }
1108
1109 #[test]
1110 fn test_nm_entry_new() {
1111 let nm = NmEntry::new(b"test_file.txt");
1112 assert!(nm.flags.is_empty());
1113 assert_eq!(nm.name, b"test_file.txt");
1114 assert_eq!(nm.size(), 5 + 13);
1115 }
1116
1117 #[test]
1118 fn test_nm_entry_special() {
1119 let current = NmEntry::current();
1120 assert!(current.flags.contains(NmFlags::CURRENT));
1121 assert!(current.name.is_empty());
1122
1123 let parent = NmEntry::parent();
1124 assert!(parent.flags.contains(NmFlags::PARENT));
1125 assert!(parent.name.is_empty());
1126 }
1127
1128 #[test]
1129 fn test_sl_component_new() {
1130 let comp = SlComponent::new(b"subdir");
1131 assert!(comp.flags.is_empty());
1132 assert_eq!(comp.content, b"subdir");
1133 assert_eq!(comp.size(), 2 + 6);
1134 }
1135
1136 #[test]
1137 fn test_sl_component_special() {
1138 let root = SlComponent::root();
1139 assert!(root.flags.contains(SlComponentFlags::ROOT));
1140 assert!(root.content.is_empty());
1141
1142 let current = SlComponent::current();
1143 assert!(current.flags.contains(SlComponentFlags::CURRENT));
1144
1145 let parent = SlComponent::parent();
1146 assert!(parent.flags.contains(SlComponentFlags::PARENT));
1147 }
1148
1149 #[test]
1150 fn test_sl_entry_from_path_absolute() {
1151 let sl = SlEntry::from_path("/usr/bin/test");
1152 assert_eq!(sl.components.len(), 4); assert!(sl.components[0].flags.contains(SlComponentFlags::ROOT));
1154 assert_eq!(sl.components[1].content, b"usr");
1155 assert_eq!(sl.components[2].content, b"bin");
1156 assert_eq!(sl.components[3].content, b"test");
1157 }
1158
1159 #[test]
1160 fn test_sl_entry_from_path_relative() {
1161 let sl = SlEntry::from_path("../lib/libtest.so");
1162 assert_eq!(sl.components.len(), 3); assert!(sl.components[0].flags.contains(SlComponentFlags::PARENT));
1164 assert_eq!(sl.components[1].content, b"lib");
1165 assert_eq!(sl.components[2].content, b"libtest.so");
1166 }
1167
1168 #[test]
1169 fn test_sl_entry_from_path_current() {
1170 let sl = SlEntry::from_path("./local");
1171 assert_eq!(sl.components.len(), 2); assert!(sl.components[0].flags.contains(SlComponentFlags::CURRENT));
1173 assert_eq!(sl.components[1].content, b"local");
1174 }
1175
1176 #[test]
1177 fn test_tf_entry_new_short() {
1178 let mtime = [126, 1, 15, 10, 30, 0, 0]; let atime = [126, 1, 15, 12, 0, 0, 0]; let tf = TfEntry::new_short(&mtime, &atime);
1181 assert!(tf.flags.contains(TfFlags::MODIFY));
1182 assert!(tf.flags.contains(TfFlags::ACCESS));
1183 assert!(!tf.flags.contains(TfFlags::LONG_FORM));
1184 assert_eq!(tf.timestamps.len(), 14);
1185 }
1186
1187 #[test]
1188 fn test_rrip_options_default() {
1189 let opts = RripOptions::default();
1190 assert!(opts.enabled);
1191 assert!(opts.relocate_deep_dirs);
1192 assert!(opts.preserve_permissions);
1193 assert!(opts.preserve_ownership);
1194 assert!(opts.preserve_timestamps);
1195 assert!(opts.preserve_symlinks);
1196 assert!(opts.preserve_devices);
1197 }
1198
1199 #[test]
1200 fn test_rrip_options_disabled() {
1201 let opts = RripOptions::disabled();
1202 assert!(!opts.enabled);
1203 assert!(!opts.relocate_deep_dirs);
1204 assert!(!opts.preserve_permissions);
1205 }
1206
1207 #[test]
1208 fn test_rrip_builder_empty() {
1209 let builder = RripBuilder::new();
1210 assert!(builder.is_empty());
1211 assert_eq!(builder.size(), 0);
1212 }
1213
1214 #[test]
1215 fn test_rrip_builder_px() {
1216 let mut builder = RripBuilder::new();
1217 builder.add_px(0o100644, 1, 1000, 1000, 12345);
1218 assert_eq!(builder.size(), 44);
1219
1220 let data = builder.build();
1221 assert_eq!(&data[0..2], b"PX");
1222 assert_eq!(data[2], 44); assert_eq!(data[3], 1); }
1225
1226 #[test]
1227 fn test_rrip_builder_pn() {
1228 let mut builder = RripBuilder::new();
1229 builder.add_pn(8, 1);
1230 assert_eq!(builder.size(), 20);
1231
1232 let data = builder.build();
1233 assert_eq!(&data[0..2], b"PN");
1234 assert_eq!(data[2], 20);
1235 }
1236
1237 #[test]
1238 fn test_rrip_builder_nm() {
1239 let mut builder = RripBuilder::new();
1240 builder.add_nm(b"test.txt");
1241 assert_eq!(builder.size(), 5 + 8); let data = builder.build();
1244 assert_eq!(&data[0..2], b"NM");
1245 assert_eq!(data[4], 0); assert_eq!(&data[5..], b"test.txt");
1247 }
1248
1249 #[test]
1250 fn test_rrip_builder_nm_special() {
1251 let mut builder = RripBuilder::new();
1252 builder.add_nm_current();
1253 let data = builder.build();
1254 assert_eq!(data[4], 0x02); let mut builder = RripBuilder::new();
1257 builder.add_nm_parent();
1258 let data = builder.build();
1259 assert_eq!(data[4], 0x04); }
1261
1262 #[test]
1263 fn test_rrip_builder_sl() {
1264 let mut builder = RripBuilder::new();
1265 builder.add_sl("../lib");
1266
1267 let data = builder.build();
1268 assert_eq!(&data[0..2], b"SL");
1269 }
1270
1271 #[test]
1272 fn test_rrip_builder_cl_pl_re() {
1273 let mut builder = RripBuilder::new();
1274 builder.add_cl(100).add_pl(50).add_re();
1275
1276 let data = builder.build();
1277 assert_eq!(&data[0..2], b"CL");
1279 assert_eq!(data[2], 12);
1280 assert_eq!(&data[12..14], b"PL");
1282 assert_eq!(data[14], 12);
1283 assert_eq!(&data[24..26], b"RE");
1285 assert_eq!(data[26], 4);
1286 }
1287
1288 #[test]
1289 fn test_rrip_builder_complete() {
1290 let mut builder = RripBuilder::new();
1291 builder
1292 .add_sp(0)
1293 .add_rrip_er()
1294 .add_px(0o100644, 1, 1000, 1000, 1)
1295 .add_nm(b"testfile.txt")
1296 .add_st();
1297
1298 let data = builder.build();
1299 assert!(!data.is_empty());
1300 assert_eq!(&data[0..2], b"SP");
1302 }
1303
1304 #[test]
1305 fn test_rrip_builder_long_name_split() {
1306 let mut builder = RripBuilder::new();
1307 let long_name = vec![b'a'; 300];
1309 builder.add_nm(&long_name);
1310
1311 let data = builder.build();
1312 assert_eq!(&data[0..2], b"NM");
1314 assert_eq!(data[4] & 0x01, 0x01);
1316 }
1317}
1318}