Skip to content

Pna/special files support#2537

Draft
ChanTsune wants to merge 5 commits into
mainfrom
pna/special-files-support
Draft

Pna/special files support#2537
ChanTsune wants to merge 5 commits into
mainfrom
pna/special-files-support

Conversation

@ChanTsune
Copy link
Copy Markdown
Owner

No description provided.

Add support for archiving block devices, character devices, and FIFOs
(named pipes) to PNA archives, following the tar pattern of storing
device metadata only.

Library changes:
- Extend DataKind enum with BlockDevice, CharDevice, Fifo variants
- Add dNUM chunk type for storing device major/minor numbers
- Add DeviceNumbers struct with serialization support
- Add EntryBuilder::new_block_device(), new_char_device(), new_fifo()
- Update NormalEntry to parse/write dNUM chunks

CLI changes:
- Detect device files using FileTypeExt on Unix
- Create device entries with correct major/minor numbers
- Display device files in list command (b/c/p type indicators)
- Skip device extraction with warning (mknod support to be added later)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add Unix device file extraction support using mknod/mkfifo:

- Add mknod_block(), mknod_char(), mkfifo() helper functions
- Extract block devices with mknod(S_IFBLK, dev)
- Extract character devices with mknod(S_IFCHR, dev)
- Extract FIFOs with mkfifo()
- Preserve permissions from archive metadata
- Log warnings for failures (e.g., missing root privileges)
- Skip gracefully on non-Unix platforms
Test FIFO and device node (block/char) round-trip archiving:
- fifo_roundtrip: creates FIFO, archives, extracts, verifies type
- device_nodes_roundtrip: creates block/char devices, verifies major/minor

Tests gracefully skip if mknod fails (containerized environments).
Refactor device file support to follow PNA's design pattern for
reference-based entries. Device numbers are now stored in FDAT
(like symlink targets) instead of a separate dNUM ancillary chunk.

Changes:
- Remove dNUM chunk type from ChunkType
- EntryBuilder::new_block_device/new_char_device now return io::Result
  and write device numbers to FDAT using WriteOptions::store()
- Remove device_numbers field from NormalEntry struct
- Replace device_numbers() with read_device_numbers() -> io::Result<Option<DeviceNumbers>>
- Update CLI to use new API

This aligns device file handling with the symlink/hardlink pattern
where reference data is stored in FDAT rather than ancillary chunks.
The read_device_numbers() method now accepts a ReadOption parameter
so it can pass the password when reading encrypted device entries.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 30, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ChanTsune, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the utility's capabilities by enabling the proper handling of Unix special file types. It allows users to archive and extract block devices, character devices, and named pipes, preserving their essential metadata and functionality. This enhancement is crucial for maintaining file system integrity and completeness when creating backups or transferring system-level data on Unix-based platforms.

Highlights

  • Special File Type Support: Introduced support for archiving and extracting Unix special file types: block devices, character devices, and FIFOs (named pipes). This includes new DataKind variants and corresponding StoreAs enum extensions.
  • Device Number Handling: Added a DeviceNumbers struct to represent major and minor device numbers, along with methods to read and write these numbers from archive entries. Utility functions for encoding/decoding rdev and creating device nodes (mknod_block, mknod_char, mkfifo) were added, guarded by #[cfg(unix)].
  • CLI Integration: The command-line interface (CLI) now recognizes, lists, and extracts these special file types. The diff command has been updated to correctly compare them, and the list command displays them with appropriate indicators.
  • Integration Tests: New integration tests (device_files.rs) have been added to verify the roundtrip functionality for FIFOs, block devices, and character devices, ensuring they can be correctly archived and extracted on Unix-like systems.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for special file types on Unix systems, including block devices, character devices, and FIFOs. The changes are comprehensive, touching the CLI commands for creation, extraction, listing, and diffing, as well as the core library to handle these new entry types. New tests have been added to verify the functionality.

My review focuses on improving maintainability by reducing code duplication and enhancing error handling. I've identified a few areas where the code can be refactored for better clarity and robustness.

Comment on lines +693 to +770
DataKind::BlockDevice => {
#[cfg(unix)]
{
let Some(dev) = item.read_device_numbers(ReadOptions::with_password(password))?
else {
log::warn!(
"Skipping block device without device numbers: {}",
path.display()
);
return Ok(());
};
let mode = item
.metadata()
.permission()
.map(|p| p.permissions() as u32)
.unwrap_or(0o666);
if remove_existing {
utils::io::ignore_not_found(utils::fs::remove_path(&path))?;
}
if let Err(e) = utils::fs::mknod_block(&path, dev.major(), dev.minor(), mode) {
log::warn!(
"Failed to create block device {} (major={}, minor={}): {} (may require root)",
path.display(),
dev.major(),
dev.minor(),
e
);
return Ok(());
}
}
#[cfg(not(unix))]
{
log::warn!(
"Skipping block device (not supported on this platform): {}",
path.display()
);
return Ok(());
}
}
DataKind::CharDevice => {
#[cfg(unix)]
{
let Some(dev) = item.read_device_numbers(ReadOptions::with_password(password))?
else {
log::warn!(
"Skipping character device without device numbers: {}",
path.display()
);
return Ok(());
};
let mode = item
.metadata()
.permission()
.map(|p| p.permissions() as u32)
.unwrap_or(0o666);
if remove_existing {
utils::io::ignore_not_found(utils::fs::remove_path(&path))?;
}
if let Err(e) = utils::fs::mknod_char(&path, dev.major(), dev.minor(), mode) {
log::warn!(
"Failed to create character device {} (major={}, minor={}): {} (may require root)",
path.display(),
dev.major(),
dev.minor(),
e
);
return Ok(());
}
}
#[cfg(not(unix))]
{
log::warn!(
"Skipping character device (not supported on this platform): {}",
path.display()
);
return Ok(());
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for handling DataKind::BlockDevice and DataKind::CharDevice is nearly identical, leading to significant code duplication. This can be refactored into a helper function to improve maintainability. The helper function could take a boolean flag to distinguish between block and character devices and encapsulate the common logic for reading device numbers, checking permissions, and creating the device node.

Comment thread cli/src/utils/fs.rs
use nix::sys::stat::{self, SFlag};
let dev = encode_rdev(major, minor);
let mode = stat::Mode::from_bits_truncate(mode);
stat::mknod(path.as_ref(), SFlag::S_IFBLK, mode, dev).map_err(io::Error::other)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using io::Error::other to wrap nix::Error can obscure the original error kind. Since nix::Error implements From<nix::Error> for io::Error, you can use Into::into to perform an idiomatic conversion, which preserves more error information. This also applies to mknod_char and mkfifo.

Suggested change
stat::mknod(path.as_ref(), SFlag::S_IFBLK, mode, dev).map_err(io::Error::other)
stat::mknod(path.as_ref(), SFlag::S_IFBLK, mode, dev).map_err(Into::into)

Comment thread cli/src/utils/fs.rs
use nix::sys::stat::{self, SFlag};
let dev = encode_rdev(major, minor);
let mode = stat::Mode::from_bits_truncate(mode);
stat::mknod(path.as_ref(), SFlag::S_IFCHR, mode, dev).map_err(io::Error::other)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using io::Error::other to wrap nix::Error can obscure the original error kind. Since nix::Error implements From<nix::Error> for io::Error, you can use Into::into to perform an idiomatic conversion, which preserves more error information.

Suggested change
stat::mknod(path.as_ref(), SFlag::S_IFCHR, mode, dev).map_err(io::Error::other)
stat::mknod(path.as_ref(), SFlag::S_IFCHR, mode, dev).map_err(Into::into)

Comment thread cli/src/utils/fs.rs
use nix::sys::stat::Mode;
use nix::unistd;
let mode = Mode::from_bits_truncate(mode);
unistd::mkfifo(path.as_ref(), mode).map_err(io::Error::other)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using io::Error::other to wrap nix::Error can obscure the original error kind. Since nix::Error implements From<nix::Error> for io::Error, you can use Into::into to perform an idiomatic conversion, which preserves more error information.

Suggested change
unistd::mkfifo(path.as_ref(), mode).map_err(io::Error::other)
unistd::mkfifo(path.as_ref(), mode).map_err(Into::into)

Comment thread lib/src/entry/builder.rs
Comment on lines +354 to +415
pub fn new_block_device(name: EntryName, major: u32, minor: u32) -> io::Result<Self> {
let option = WriteOptions::store();
let context = get_writer_context(option)?;
let mut writer = get_writer(FlattenWriter::new(), &context)?;
writer.write_all(&DeviceNumbers::new(major, minor).to_bytes())?;
let (iv, phsf) = match context.cipher {
None => (None, None),
Some(WriteCipher { context: c, .. }) => (Some(c.iv), Some(c.phsf)),
};
Ok(Self {
data: Some(writer),
iv,
phsf,
..Self::new(EntryHeader::for_block_device(name))
})
}

/// Creates a new character device entry with the given name and device numbers.
///
/// # Arguments
///
/// * `name` - The name of the entry to create.
/// * `major` - The major device number.
/// * `minor` - The minor device number.
///
/// # Returns
///
/// A new [EntryBuilder].
///
/// # Errors
///
/// Returns an error if initialization fails.
///
/// # Examples
/// ```
/// use libpna::{EntryBuilder, EntryName};
///
/// // Create a character device entry (e.g., /dev/null with major=1, minor=3)
/// let builder = EntryBuilder::new_char_device(
/// EntryName::try_from("dev/null").unwrap(),
/// 1,
/// 3,
/// ).unwrap();
/// let entry = builder.build().unwrap();
/// ```
#[inline]
pub fn new_char_device(name: EntryName, major: u32, minor: u32) -> io::Result<Self> {
let option = WriteOptions::store();
let context = get_writer_context(option)?;
let mut writer = get_writer(FlattenWriter::new(), &context)?;
writer.write_all(&DeviceNumbers::new(major, minor).to_bytes())?;
let (iv, phsf) = match context.cipher {
None => (None, None),
Some(WriteCipher { context: c, .. }) => (Some(c.iv), Some(c.phsf)),
};
Ok(Self {
data: Some(writer),
iv,
phsf,
..Self::new(EntryHeader::for_char_device(name))
})
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The functions new_block_device and new_char_device are almost identical, leading to code duplication. You can refactor this by creating a private helper function that takes a boolean flag to distinguish between block and character devices. This would centralize the logic for creating device entries and improve maintainability.

@github-actions github-actions Bot added cli This issue is about cli application lib This issue is about lib crate break API braking change labels Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

break API braking change cli This issue is about cli application lib This issue is about lib crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant