1//! Support for archive files.
2//!
3//! ## Example
4//! ```no_run
5//! use object::{Object, ObjectSection};
6//! use std::error::Error;
7//! use std::fs;
8//!
9//! /// Reads an archive and displays the name of each member.
10//! fn main() -> Result<(), Box<dyn Error>> {
11//! # #[cfg(feature = "std")] {
12//! let data = fs::read("path/to/binary")?;
13//! let file = object::read::archive::ArchiveFile::parse(&*data)?;
14//! for member in file.members() {
15//! let member = member?;
16//! println!("{}", String::from_utf8_lossy(member.name()));
17//! }
18//! # }
19//! Ok(())
20//! }
21//! ```
22
23use core::convert::TryInto;
24
25use crate::archive;
26use crate::read::{self, Bytes, Error, ReadError, ReadRef};
27
28/// The kind of archive format.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum ArchiveKind {
32 /// There are no special files that indicate the archive format.
33 Unknown,
34 /// The GNU (or System V) archive format.
35 Gnu,
36 /// The GNU (or System V) archive format with 64-bit symbol table.
37 Gnu64,
38 /// The BSD archive format.
39 Bsd,
40 /// The BSD archive format with 64-bit symbol table.
41 ///
42 /// This is used for Darwin.
43 Bsd64,
44 /// The Windows COFF archive format.
45 Coff,
46 /// The AIX big archive format.
47 AixBig,
48}
49
50/// The list of members in the archive.
51#[derive(Debug, Clone, Copy)]
52enum Members<'data> {
53 Common {
54 offset: u64,
55 end_offset: u64,
56 },
57 AixBig {
58 index: &'data [archive::AixMemberOffset],
59 },
60}
61
62/// A partially parsed archive file.
63#[derive(Debug, Clone, Copy)]
64pub struct ArchiveFile<'data, R: ReadRef<'data> = &'data [u8]> {
65 data: R,
66 kind: ArchiveKind,
67 members: Members<'data>,
68 symbols: (u64, u64),
69 names: &'data [u8],
70}
71
72impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
73 /// Parse the archive header and special members.
74 pub fn parse(data: R) -> read::Result<Self> {
75 let len = data.len().read_error("Unknown archive length")?;
76 let mut tail = 0;
77 let magic = data
78 .read_bytes(&mut tail, archive::MAGIC.len() as u64)
79 .read_error("Invalid archive size")?;
80
81 if magic == archive::AIX_BIG_MAGIC {
82 return Self::parse_aixbig(data);
83 } else if magic != archive::MAGIC {
84 return Err(Error("Unsupported archive identifier"));
85 }
86
87 let mut members_offset = tail;
88 let members_end_offset = len;
89
90 let mut file = ArchiveFile {
91 data,
92 kind: ArchiveKind::Unknown,
93 members: Members::Common {
94 offset: 0,
95 end_offset: 0,
96 },
97 symbols: (0, 0),
98 names: &[],
99 };
100
101 // The first few members may be special, so parse them.
102 // GNU has:
103 // - "/" or "/SYM64/": symbol table (optional)
104 // - "//": names table (optional)
105 // COFF has:
106 // - "/": first linker member
107 // - "/": second linker member
108 // - "//": names table
109 // BSD has:
110 // - "__.SYMDEF" or "__.SYMDEF SORTED": symbol table (optional)
111 // BSD 64-bit has:
112 // - "__.SYMDEF_64" or "__.SYMDEF_64 SORTED": symbol table (optional)
113 // BSD may use the extended name for the symbol table. This is handled
114 // by `ArchiveMember::parse`.
115 if tail < len {
116 let member = ArchiveMember::parse(data, &mut tail, &[])?;
117 if member.name == b"/" {
118 // GNU symbol table (unless we later determine this is COFF).
119 file.kind = ArchiveKind::Gnu;
120 file.symbols = member.file_range();
121 members_offset = tail;
122
123 if tail < len {
124 let member = ArchiveMember::parse(data, &mut tail, &[])?;
125 if member.name == b"/" {
126 // COFF linker member.
127 file.kind = ArchiveKind::Coff;
128 file.symbols = member.file_range();
129 members_offset = tail;
130
131 if tail < len {
132 let member = ArchiveMember::parse(data, &mut tail, &[])?;
133 if member.name == b"//" {
134 // COFF names table.
135 file.names = member.data(data)?;
136 members_offset = tail;
137 }
138 }
139 } else if member.name == b"//" {
140 // GNU names table.
141 file.names = member.data(data)?;
142 members_offset = tail;
143 }
144 }
145 } else if member.name == b"/SYM64/" {
146 // GNU 64-bit symbol table.
147 file.kind = ArchiveKind::Gnu64;
148 file.symbols = member.file_range();
149 members_offset = tail;
150
151 if tail < len {
152 let member = ArchiveMember::parse(data, &mut tail, &[])?;
153 if member.name == b"//" {
154 // GNU names table.
155 file.names = member.data(data)?;
156 members_offset = tail;
157 }
158 }
159 } else if member.name == b"//" {
160 // GNU names table.
161 file.kind = ArchiveKind::Gnu;
162 file.names = member.data(data)?;
163 members_offset = tail;
164 } else if member.name == b"__.SYMDEF" || member.name == b"__.SYMDEF SORTED" {
165 // BSD symbol table.
166 file.kind = ArchiveKind::Bsd;
167 file.symbols = member.file_range();
168 members_offset = tail;
169 } else if member.name == b"__.SYMDEF_64" || member.name == b"__.SYMDEF_64 SORTED" {
170 // BSD 64-bit symbol table.
171 file.kind = ArchiveKind::Bsd64;
172 file.symbols = member.file_range();
173 members_offset = tail;
174 } else {
175 // TODO: This could still be a BSD file. We leave this as unknown for now.
176 }
177 }
178 file.members = Members::Common {
179 offset: members_offset,
180 end_offset: members_end_offset,
181 };
182 Ok(file)
183 }
184
185 fn parse_aixbig(data: R) -> read::Result<Self> {
186 let mut tail = 0;
187
188 let file_header = data
189 .read::<archive::AixFileHeader>(&mut tail)
190 .read_error("Invalid AIX big archive file header")?;
191 // Caller already validated this.
192 debug_assert_eq!(file_header.magic, archive::AIX_BIG_MAGIC);
193
194 let mut file = ArchiveFile {
195 data,
196 kind: ArchiveKind::AixBig,
197 members: Members::AixBig { index: &[] },
198 symbols: (0, 0),
199 names: &[],
200 };
201
202 // Read the span of symbol table.
203 let symtbl64 = parse_u64_digits(&file_header.gst64off, 10)
204 .read_error("Invalid offset to 64-bit symbol table in AIX big archive")?;
205 if symtbl64 > 0 {
206 // The symbol table is also a file with header.
207 let member = ArchiveMember::parse_aixbig(data, symtbl64)?;
208 file.symbols = member.file_range();
209 } else {
210 let symtbl = parse_u64_digits(&file_header.gstoff, 10)
211 .read_error("Invalid offset to symbol table in AIX big archive")?;
212 if symtbl > 0 {
213 // The symbol table is also a file with header.
214 let member = ArchiveMember::parse_aixbig(data, symtbl)?;
215 file.symbols = member.file_range();
216 }
217 }
218
219 // Big archive member index table lists file entries with offsets and names.
220 // To avoid potential infinite loop (members are double-linked list), the
221 // iterator goes through the index instead of real members.
222 let member_table_offset = parse_u64_digits(&file_header.memoff, 10)
223 .read_error("Invalid offset for member table of AIX big archive")?;
224 if member_table_offset == 0 {
225 // The offset would be zero if archive contains no file.
226 return Ok(file);
227 }
228
229 // The member index table is also a file with header.
230 let member = ArchiveMember::parse_aixbig(data, member_table_offset)?;
231 let mut member_data = Bytes(member.data(data)?);
232
233 // Structure of member index table:
234 // Number of entries (20 bytes)
235 // Offsets of each entry (20*N bytes)
236 // Names string table (the rest of bytes to fill size defined in header)
237 let members_count_bytes = member_data
238 .read_slice::<u8>(20)
239 .read_error("Missing member count in AIX big archive")?;
240 let members_count = parse_u64_digits(members_count_bytes, 10)
241 .and_then(|size| size.try_into().ok())
242 .read_error("Invalid member count in AIX big archive")?;
243 let index = member_data
244 .read_slice::<archive::AixMemberOffset>(members_count)
245 .read_error("Member count overflow in AIX big archive")?;
246 file.members = Members::AixBig { index };
247
248 Ok(file)
249 }
250
251 /// Return the archive format.
252 #[inline]
253 pub fn kind(&self) -> ArchiveKind {
254 self.kind
255 }
256
257 /// Iterate over the members of the archive.
258 ///
259 /// This does not return special members.
260 #[inline]
261 pub fn members(&self) -> ArchiveMemberIterator<'data, R> {
262 ArchiveMemberIterator {
263 data: self.data,
264 members: self.members,
265 names: self.names,
266 }
267 }
268}
269
270/// An iterator over the members of an archive.
271#[derive(Debug)]
272pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
273 data: R,
274 members: Members<'data>,
275 names: &'data [u8],
276}
277
278impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
279 type Item = read::Result<ArchiveMember<'data>>;
280
281 fn next(&mut self) -> Option<Self::Item> {
282 match &mut self.members {
283 Members::Common {
284 ref mut offset,
285 ref mut end_offset,
286 } => {
287 if *offset >= *end_offset {
288 return None;
289 }
290 let member = ArchiveMember::parse(self.data, offset, self.names);
291 if member.is_err() {
292 *offset = *end_offset;
293 }
294 Some(member)
295 }
296 Members::AixBig { ref mut index } => match **index {
297 [] => None,
298 [ref first, ref rest @ ..] => {
299 *index = rest;
300 let member = ArchiveMember::parse_aixbig_index(self.data, first);
301 if member.is_err() {
302 *index = &[];
303 }
304 Some(member)
305 }
306 },
307 }
308 }
309}
310
311/// An archive member header.
312#[derive(Debug, Clone, Copy)]
313enum MemberHeader<'data> {
314 /// Common header used by many formats.
315 Common(&'data archive::Header),
316 /// AIX big archive header
317 AixBig(&'data archive::AixHeader),
318}
319
320/// A partially parsed archive member.
321#[derive(Debug)]
322pub struct ArchiveMember<'data> {
323 header: MemberHeader<'data>,
324 name: &'data [u8],
325 offset: u64,
326 size: u64,
327}
328
329impl<'data> ArchiveMember<'data> {
330 /// Parse the member header, name, and file data in an archive with the common format.
331 ///
332 /// This reads the extended name (if any) and adjusts the file size.
333 fn parse<R: ReadRef<'data>>(
334 data: R,
335 offset: &mut u64,
336 names: &'data [u8],
337 ) -> read::Result<Self> {
338 let header = data
339 .read::<archive::Header>(offset)
340 .read_error("Invalid archive member header")?;
341 if header.terminator != archive::TERMINATOR {
342 return Err(Error("Invalid archive terminator"));
343 }
344
345 let mut file_offset = *offset;
346 let mut file_size =
347 parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
348 *offset = offset
349 .checked_add(file_size)
350 .read_error("Archive member size is too large")?;
351 // Entries are padded to an even number of bytes.
352 if (file_size & 1) != 0 {
353 *offset = offset.saturating_add(1);
354 }
355
356 let name = if header.name[0] == b'/' && (header.name[1] as char).is_ascii_digit() {
357 // Read file name from the names table.
358 parse_sysv_extended_name(&header.name[1..], names)
359 .read_error("Invalid archive extended name offset")?
360 } else if &header.name[..3] == b"#1/" && (header.name[3] as char).is_ascii_digit() {
361 // Read file name from the start of the file data.
362 parse_bsd_extended_name(&header.name[3..], data, &mut file_offset, &mut file_size)
363 .read_error("Invalid archive extended name length")?
364 } else if header.name[0] == b'/' {
365 let name_len = memchr::memchr(b' ', &header.name).unwrap_or(header.name.len());
366 &header.name[..name_len]
367 } else {
368 let name_len = memchr::memchr(b'/', &header.name)
369 .or_else(|| memchr::memchr(b' ', &header.name))
370 .unwrap_or(header.name.len());
371 &header.name[..name_len]
372 };
373
374 Ok(ArchiveMember {
375 header: MemberHeader::Common(header),
376 name,
377 offset: file_offset,
378 size: file_size,
379 })
380 }
381
382 /// Parse a member index entry in an AIX big archive,
383 /// and then parse the member header, name, and file data.
384 fn parse_aixbig_index<R: ReadRef<'data>>(
385 data: R,
386 index: &archive::AixMemberOffset,
387 ) -> read::Result<Self> {
388 let offset = parse_u64_digits(&index.0, 10)
389 .read_error("Invalid AIX big archive file member offset")?;
390 Self::parse_aixbig(data, offset)
391 }
392
393 /// Parse the member header, name, and file data in an AIX big archive.
394 fn parse_aixbig<R: ReadRef<'data>>(data: R, mut offset: u64) -> read::Result<Self> {
395 // The format was described at
396 // https://www.ibm.com/docs/en/aix/7.3?topic=formats-ar-file-format-big
397 let header = data
398 .read::<archive::AixHeader>(&mut offset)
399 .read_error("Invalid AIX big archive member header")?;
400 let name_length = parse_u64_digits(&header.namlen, 10)
401 .read_error("Invalid AIX big archive member name length")?;
402 let name = data
403 .read_bytes(&mut offset, name_length)
404 .read_error("Invalid AIX big archive member name")?;
405
406 // The actual data for a file member begins at the first even-byte boundary beyond the
407 // member header and continues for the number of bytes specified by the ar_size field. The
408 // ar command inserts null bytes for padding where necessary.
409 if offset & 1 != 0 {
410 offset = offset.saturating_add(1);
411 }
412 // Because of the even-byte boundary, we have to read and check terminator after header.
413 let terminator = data
414 .read_bytes(&mut offset, 2)
415 .read_error("Invalid AIX big archive terminator")?;
416 if terminator != archive::TERMINATOR {
417 return Err(Error("Invalid AIX big archive terminator"));
418 }
419
420 let size = parse_u64_digits(&header.size, 10)
421 .read_error("Invalid archive member size in AIX big archive")?;
422 Ok(ArchiveMember {
423 header: MemberHeader::AixBig(header),
424 name,
425 offset,
426 size,
427 })
428 }
429
430 /// Return the raw header that is common to many archive formats.
431 ///
432 /// Returns `None` if this archive does not use the common header format.
433 #[inline]
434 pub fn header(&self) -> Option<&'data archive::Header> {
435 match self.header {
436 MemberHeader::Common(header) => Some(header),
437 _ => None,
438 }
439 }
440
441 /// Return the raw header for AIX big archives.
442 ///
443 /// Returns `None` if this is not an AIX big archive.
444 #[inline]
445 pub fn aix_header(&self) -> Option<&'data archive::AixHeader> {
446 match self.header {
447 MemberHeader::AixBig(header) => Some(header),
448 _ => None,
449 }
450 }
451
452 /// Return the parsed file name.
453 ///
454 /// This may be an extended file name.
455 #[inline]
456 pub fn name(&self) -> &'data [u8] {
457 self.name
458 }
459
460 /// Parse the file modification timestamp from the header.
461 #[inline]
462 pub fn date(&self) -> Option<u64> {
463 match &self.header {
464 MemberHeader::Common(header) => parse_u64_digits(&header.date, 10),
465 MemberHeader::AixBig(header) => parse_u64_digits(&header.date, 10),
466 }
467 }
468
469 /// Parse the user ID from the header.
470 #[inline]
471 pub fn uid(&self) -> Option<u64> {
472 match &self.header {
473 MemberHeader::Common(header) => parse_u64_digits(&header.uid, 10),
474 MemberHeader::AixBig(header) => parse_u64_digits(&header.uid, 10),
475 }
476 }
477
478 /// Parse the group ID from the header.
479 #[inline]
480 pub fn gid(&self) -> Option<u64> {
481 match &self.header {
482 MemberHeader::Common(header) => parse_u64_digits(&header.gid, 10),
483 MemberHeader::AixBig(header) => parse_u64_digits(&header.gid, 10),
484 }
485 }
486
487 /// Parse the file mode from the header.
488 #[inline]
489 pub fn mode(&self) -> Option<u64> {
490 match &self.header {
491 MemberHeader::Common(header) => parse_u64_digits(&header.mode, 8),
492 MemberHeader::AixBig(header) => parse_u64_digits(&header.mode, 8),
493 }
494 }
495
496 /// Return the offset and size of the file data.
497 pub fn file_range(&self) -> (u64, u64) {
498 (self.offset, self.size)
499 }
500
501 /// Return the file data.
502 #[inline]
503 pub fn data<R: ReadRef<'data>>(&self, data: R) -> read::Result<&'data [u8]> {
504 data.read_bytes_at(self.offset, self.size)
505 .read_error("Archive member size is too large")
506 }
507}
508
509// Ignores bytes starting from the first space.
510fn parse_u64_digits(digits: &[u8], radix: u32) -> Option<u64> {
511 if let [b' ', ..] = digits {
512 return None;
513 }
514 let mut result: u64 = 0;
515 for &c: u8 in digits {
516 if c == b' ' {
517 return Some(result);
518 } else {
519 let x: u32 = (c as char).to_digit(radix)?;
520 result = resultu64
521 .checked_mul(u64::from(radix))?
522 .checked_add(u64::from(x))?;
523 }
524 }
525 Some(result)
526}
527
528fn parse_sysv_extended_name<'data>(digits: &[u8], names: &'data [u8]) -> Result<&'data [u8], ()> {
529 let offset: u64 = parse_u64_digits(digits, 10).ok_or(())?;
530 let offset: usize = offset.try_into().map_err(|_| ())?;
531 let name_data: &[u8] = names.get(offset..).ok_or(())?;
532 let name: &[u8] = match memchr::memchr2(needle1:b'/', needle2:b'\0', haystack:name_data) {
533 Some(len: usize) => &name_data[..len],
534 None => name_data,
535 };
536 Ok(name)
537}
538
539/// Modifies `data` to start after the extended name.
540fn parse_bsd_extended_name<'data, R: ReadRef<'data>>(
541 digits: &[u8],
542 data: R,
543 offset: &mut u64,
544 size: &mut u64,
545) -> Result<&'data [u8], ()> {
546 let len: u64 = parse_u64_digits(digits, 10).ok_or(())?;
547 *size = size.checked_sub(len).ok_or(())?;
548 let name_data: &[u8] = data.read_bytes(offset, size:len)?;
549 let name: &[u8] = match memchr::memchr(needle:b'\0', haystack:name_data) {
550 Some(len: usize) => &name_data[..len],
551 None => name_data,
552 };
553 Ok(name)
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn kind() {
562 let data = b"!<arch>\n";
563 let archive = ArchiveFile::parse(&data[..]).unwrap();
564 assert_eq!(archive.kind(), ArchiveKind::Unknown);
565
566 let data = b"\
567 !<arch>\n\
568 / 4 `\n\
569 0000";
570 let archive = ArchiveFile::parse(&data[..]).unwrap();
571 assert_eq!(archive.kind(), ArchiveKind::Gnu);
572
573 let data = b"\
574 !<arch>\n\
575 // 4 `\n\
576 0000";
577 let archive = ArchiveFile::parse(&data[..]).unwrap();
578 assert_eq!(archive.kind(), ArchiveKind::Gnu);
579
580 let data = b"\
581 !<arch>\n\
582 / 4 `\n\
583 0000\
584 // 4 `\n\
585 0000";
586 let archive = ArchiveFile::parse(&data[..]).unwrap();
587 assert_eq!(archive.kind(), ArchiveKind::Gnu);
588
589 let data = b"\
590 !<arch>\n\
591 /SYM64/ 4 `\n\
592 0000";
593 let archive = ArchiveFile::parse(&data[..]).unwrap();
594 assert_eq!(archive.kind(), ArchiveKind::Gnu64);
595
596 let data = b"\
597 !<arch>\n\
598 /SYM64/ 4 `\n\
599 0000\
600 // 4 `\n\
601 0000";
602 let archive = ArchiveFile::parse(&data[..]).unwrap();
603 assert_eq!(archive.kind(), ArchiveKind::Gnu64);
604
605 let data = b"\
606 !<arch>\n\
607 __.SYMDEF 4 `\n\
608 0000";
609 let archive = ArchiveFile::parse(&data[..]).unwrap();
610 assert_eq!(archive.kind(), ArchiveKind::Bsd);
611
612 let data = b"\
613 !<arch>\n\
614 #1/9 13 `\n\
615 __.SYMDEF0000";
616 let archive = ArchiveFile::parse(&data[..]).unwrap();
617 assert_eq!(archive.kind(), ArchiveKind::Bsd);
618
619 let data = b"\
620 !<arch>\n\
621 #1/16 20 `\n\
622 __.SYMDEF SORTED0000";
623 let archive = ArchiveFile::parse(&data[..]).unwrap();
624 assert_eq!(archive.kind(), ArchiveKind::Bsd);
625
626 let data = b"\
627 !<arch>\n\
628 __.SYMDEF_64 4 `\n\
629 0000";
630 let archive = ArchiveFile::parse(&data[..]).unwrap();
631 assert_eq!(archive.kind(), ArchiveKind::Bsd64);
632
633 let data = b"\
634 !<arch>\n\
635 #1/12 16 `\n\
636 __.SYMDEF_640000";
637 let archive = ArchiveFile::parse(&data[..]).unwrap();
638 assert_eq!(archive.kind(), ArchiveKind::Bsd64);
639
640 let data = b"\
641 !<arch>\n\
642 #1/19 23 `\n\
643 __.SYMDEF_64 SORTED0000";
644 let archive = ArchiveFile::parse(&data[..]).unwrap();
645 assert_eq!(archive.kind(), ArchiveKind::Bsd64);
646
647 let data = b"\
648 !<arch>\n\
649 / 4 `\n\
650 0000\
651 / 4 `\n\
652 0000\
653 // 4 `\n\
654 0000";
655 let archive = ArchiveFile::parse(&data[..]).unwrap();
656 assert_eq!(archive.kind(), ArchiveKind::Coff);
657
658 let data = b"\
659 <bigaf>\n\
660 0 0 \
661 0 0 \
662 0 128 \
663 6 0 \
664 0 \0\0\0\0\0\0\0\0\0\0\0\0\
665 \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
666 \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
667 \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
668 let archive = ArchiveFile::parse(&data[..]).unwrap();
669 assert_eq!(archive.kind(), ArchiveKind::AixBig);
670 }
671
672 #[test]
673 fn gnu_names() {
674 let data = b"\
675 !<arch>\n\
676 // 18 `\n\
677 0123456789abcdef/\n\
678 s p a c e/ 0 0 0 644 4 `\n\
679 0000\
680 0123456789abcde/0 0 0 644 3 `\n\
681 odd\n\
682 /0 0 0 0 644 4 `\n\
683 even";
684 let data = &data[..];
685 let archive = ArchiveFile::parse(data).unwrap();
686 assert_eq!(archive.kind(), ArchiveKind::Gnu);
687 let mut members = archive.members();
688
689 let member = members.next().unwrap().unwrap();
690 assert_eq!(member.name(), b"s p a c e");
691 assert_eq!(member.data(data).unwrap(), &b"0000"[..]);
692
693 let member = members.next().unwrap().unwrap();
694 assert_eq!(member.name(), b"0123456789abcde");
695 assert_eq!(member.data(data).unwrap(), &b"odd"[..]);
696
697 let member = members.next().unwrap().unwrap();
698 assert_eq!(member.name(), b"0123456789abcdef");
699 assert_eq!(member.data(data).unwrap(), &b"even"[..]);
700
701 assert!(members.next().is_none());
702 }
703
704 #[test]
705 fn bsd_names() {
706 let data = b"\
707 !<arch>\n\
708 0123456789abcde 0 0 0 644 3 `\n\
709 odd\n\
710 #1/16 0 0 0 644 20 `\n\
711 0123456789abcdefeven";
712 let data = &data[..];
713 let archive = ArchiveFile::parse(data).unwrap();
714 assert_eq!(archive.kind(), ArchiveKind::Unknown);
715 let mut members = archive.members();
716
717 let member = members.next().unwrap().unwrap();
718 assert_eq!(member.name(), b"0123456789abcde");
719 assert_eq!(member.data(data).unwrap(), &b"odd"[..]);
720
721 let member = members.next().unwrap().unwrap();
722 assert_eq!(member.name(), b"0123456789abcdef");
723 assert_eq!(member.data(data).unwrap(), &b"even"[..]);
724
725 assert!(members.next().is_none());
726 }
727
728 #[test]
729 fn aix_names() {
730 let data = b"\
731 <bigaf>\n\
732 396 0 0 \
733 128 262 0 \
734 4 262 0 \
735 1662610370 223 1 644 16 \
736 0123456789abcdef`\nord\n\
737 4 396 128 \
738 1662610374 223 1 644 16 \
739 fedcba9876543210`\nrev\n\
740 94 0 262 \
741 0 0 0 0 0 \
742 `\n2 128 \
743 262 0123456789abcdef\0fedcba9876543210\0";
744 let data = &data[..];
745 let archive = ArchiveFile::parse(data).unwrap();
746 assert_eq!(archive.kind(), ArchiveKind::AixBig);
747 let mut members = archive.members();
748
749 let member = members.next().unwrap().unwrap();
750 assert_eq!(member.name(), b"0123456789abcdef");
751 assert_eq!(member.data(data).unwrap(), &b"ord\n"[..]);
752
753 let member = members.next().unwrap().unwrap();
754 assert_eq!(member.name(), b"fedcba9876543210");
755 assert_eq!(member.data(data).unwrap(), &b"rev\n"[..]);
756
757 assert!(members.next().is_none());
758 }
759}
760