Skip to content

Commit 9dbc850

Browse files
committed
merge pax
1 parent 7e002ff commit 9dbc850

File tree

34 files changed

+12809
-0
lines changed

34 files changed

+12809
-0
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ members = [
1717
"gettext-rs",
1818
"misc",
1919
"pathnames",
20+
"pax",
2021
"plib",
2122
"process",
2223
"sccs",

pax/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "posixutils-pax"
3+
version = "0.2.2"
4+
authors = ["Jeff Garzik"]
5+
repository.workspace = true
6+
license.workspace = true
7+
edition.workspace = true
8+
rust-version.workspace = true
9+
10+
[features]
11+
# Run all tests including those that require special environments
12+
posixutils_test_all = []
13+
# Tests that require root privileges (mknod, device creation)
14+
requires_root = ["posixutils_test_all"]
15+
16+
[dependencies]
17+
libc.workspace = true
18+
clap.workspace = true
19+
gettext-rs.workspace = true
20+
plib = { path = "../plib" }
21+
22+
[dev-dependencies]
23+
tempfile = "3"
24+
filetime = "0.2"
25+
26+
[[bin]]
27+
name = "pax"
28+
path = "./main.rs"
29+

pax/archive.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//
2+
// Copyright (c) 2024 Jeff Garzik
3+
//
4+
// This file is part of the pax-rs project covered under
5+
// the MIT License. For the full license text, please see the LICENSE
6+
// file in the root directory of this project.
7+
// SPDX-License-Identifier: MIT
8+
//
9+
10+
use crate::error::PaxResult;
11+
use std::collections::HashMap;
12+
use std::path::{Path, PathBuf};
13+
14+
/// Type of archive entry
15+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16+
pub enum EntryType {
17+
/// Regular file
18+
#[default]
19+
Regular,
20+
/// Directory
21+
Directory,
22+
/// Symbolic link
23+
Symlink,
24+
/// Hard link to another file
25+
Hardlink,
26+
/// Block device
27+
BlockDevice,
28+
/// Character device
29+
CharDevice,
30+
/// FIFO (named pipe)
31+
Fifo,
32+
/// Socket (not typically stored in archives, but recognized)
33+
Socket,
34+
}
35+
36+
/// Metadata for an archive entry
37+
#[derive(Debug, Clone, Default)]
38+
pub struct ArchiveEntry {
39+
/// Path of the file within the archive
40+
pub path: PathBuf,
41+
/// File mode (permissions)
42+
pub mode: u32,
43+
/// User ID
44+
pub uid: u32,
45+
/// Group ID
46+
pub gid: u32,
47+
/// File size in bytes
48+
pub size: u64,
49+
/// Modification time (seconds since epoch)
50+
pub mtime: u64,
51+
/// Modification time nanoseconds (for pax format)
52+
pub mtime_nsec: u32,
53+
/// Access time (seconds since epoch, for pax format)
54+
pub atime: Option<u64>,
55+
/// Access time nanoseconds (for pax format)
56+
pub atime_nsec: u32,
57+
/// Type of entry
58+
pub entry_type: EntryType,
59+
/// Link target for symlinks and hardlinks
60+
pub link_target: Option<PathBuf>,
61+
/// User name (optional)
62+
pub uname: Option<String>,
63+
/// Group name (optional)
64+
pub gname: Option<String>,
65+
/// Device ID (for hard link tracking)
66+
pub dev: u64,
67+
/// Inode number (for hard link tracking)
68+
pub ino: u64,
69+
/// Number of hard links
70+
pub nlink: u32,
71+
/// Device major number (for block/char devices)
72+
pub devmajor: u32,
73+
/// Device minor number (for block/char devices)
74+
pub devminor: u32,
75+
}
76+
77+
impl ArchiveEntry {
78+
/// Create a new archive entry with default values
79+
pub fn new(path: PathBuf, entry_type: EntryType) -> Self {
80+
ArchiveEntry {
81+
path,
82+
mode: 0o644,
83+
uid: 0,
84+
gid: 0,
85+
size: 0,
86+
mtime: 0,
87+
mtime_nsec: 0,
88+
atime: None,
89+
atime_nsec: 0,
90+
entry_type,
91+
link_target: None,
92+
uname: None,
93+
gname: None,
94+
dev: 0,
95+
ino: 0,
96+
nlink: 1,
97+
devmajor: 0,
98+
devminor: 0,
99+
}
100+
}
101+
102+
/// Check if this entry is a special device file
103+
pub fn is_device(&self) -> bool {
104+
matches!(
105+
self.entry_type,
106+
EntryType::BlockDevice | EntryType::CharDevice
107+
)
108+
}
109+
110+
/// Check if this is a directory
111+
pub fn is_dir(&self) -> bool {
112+
self.entry_type == EntryType::Directory
113+
}
114+
}
115+
116+
/// Trait for reading archives
117+
pub trait ArchiveReader {
118+
/// Read the next entry from the archive
119+
/// Returns None when the archive is exhausted
120+
fn read_entry(&mut self) -> PaxResult<Option<ArchiveEntry>>;
121+
122+
/// Read the data for the current entry
123+
fn read_data(&mut self, buf: &mut [u8]) -> PaxResult<usize>;
124+
125+
/// Skip the data for the current entry
126+
fn skip_data(&mut self) -> PaxResult<()>;
127+
}
128+
129+
/// Trait for writing archives
130+
pub trait ArchiveWriter {
131+
/// Write an entry header to the archive
132+
fn write_entry(&mut self, entry: &ArchiveEntry) -> PaxResult<()>;
133+
134+
/// Write data for the current entry
135+
fn write_data(&mut self, data: &[u8]) -> PaxResult<()>;
136+
137+
/// Finish writing data for the current entry (handles padding)
138+
fn finish_entry(&mut self) -> PaxResult<()>;
139+
140+
/// Write the archive trailer
141+
fn finish(&mut self) -> PaxResult<()>;
142+
}
143+
144+
/// Tracks hard links during archive creation
145+
#[derive(Debug, Default)]
146+
pub struct HardLinkTracker {
147+
/// Maps (dev, ino) to the first path seen
148+
seen: HashMap<(u64, u64), PathBuf>,
149+
}
150+
151+
impl HardLinkTracker {
152+
/// Create a new tracker
153+
pub fn new() -> Self {
154+
HardLinkTracker {
155+
seen: HashMap::new(),
156+
}
157+
}
158+
159+
/// Check if we've seen this file before (by dev/ino)
160+
/// Returns the original path if this is a hard link
161+
pub fn check(&mut self, entry: &ArchiveEntry) -> Option<PathBuf> {
162+
if entry.nlink <= 1 {
163+
return None;
164+
}
165+
166+
let key = (entry.dev, entry.ino);
167+
if let Some(original) = self.seen.get(&key) {
168+
Some(original.clone())
169+
} else {
170+
self.seen.insert(key, entry.path.clone());
171+
None
172+
}
173+
}
174+
}
175+
176+
/// Tracks extracted files for hard link creation during extraction
177+
#[derive(Debug, Default)]
178+
pub struct ExtractedLinks {
179+
/// Maps (dev, ino) to the extracted path
180+
extracted: HashMap<(u64, u64), PathBuf>,
181+
}
182+
183+
impl ExtractedLinks {
184+
/// Create a new tracker
185+
pub fn new() -> Self {
186+
ExtractedLinks {
187+
extracted: HashMap::new(),
188+
}
189+
}
190+
191+
/// Record that we extracted a file
192+
pub fn record(&mut self, entry: &ArchiveEntry, path: &Path) {
193+
if entry.nlink > 1 {
194+
let key = (entry.dev, entry.ino);
195+
self.extracted
196+
.entry(key)
197+
.or_insert_with(|| path.to_path_buf());
198+
}
199+
}
200+
201+
/// Get the path to link to, if this is a hard link
202+
pub fn get_link_target(&self, entry: &ArchiveEntry) -> Option<&PathBuf> {
203+
if entry.nlink <= 1 {
204+
return None;
205+
}
206+
let key = (entry.dev, entry.ino);
207+
self.extracted.get(&key)
208+
}
209+
}
210+
211+
/// Archive format type
212+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213+
pub enum ArchiveFormat {
214+
/// POSIX ustar tar format
215+
Ustar,
216+
/// POSIX cpio format
217+
Cpio,
218+
/// POSIX pax format (extended tar with extended headers)
219+
Pax,
220+
}
221+
222+
impl std::fmt::Display for ArchiveFormat {
223+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224+
match self {
225+
ArchiveFormat::Ustar => write!(f, "ustar"),
226+
ArchiveFormat::Cpio => write!(f, "cpio"),
227+
ArchiveFormat::Pax => write!(f, "pax"),
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)