Skip to main content

HfsPlusFilesystem

Struct HfsPlusFilesystem 

Source
pub struct HfsPlusFilesystem<R: Read + Seek> { /* private fields */ }
Expand description

HFS+ filesystem implementation.

Implementations§

Source§

impl<R: Read + Seek> HfsPlusFilesystem<R>

Source

pub fn open(reader: R, partition_offset: u64) -> Result<Self, FilesystemError>

Source

pub fn open_sized( reader: R, partition_offset: u64, partition_size: Option<u64>, ) -> Result<Self, FilesystemError>

open with the partition length, so the alternate volume header is written where Apple’s tools look for it.

Source

pub fn block_size(&self) -> u32

Source

pub fn is_journaled(&self) -> bool

True when the volume header’s journaled bit is set.

Source

pub fn journal_detail( &mut self, ) -> Result<Option<JournalDetail>, FilesystemError>

Decode the journal into per-transaction, per-block detail for the GUI history viewer (Step 29). Block previews are capped; classification is against [metadata_regions]. Ok(None) when not journaled.

Source

pub fn read_journal(&mut self) -> Result<Option<JournalState>, FilesystemError>

Source

pub fn snapshot(&self) -> HfsPlusSnapshot

Capture the full mutable state for snapshot/rollback. Cheap on the scale of a single mutation: catalog + bitmap clone is O(volume metadata size), not O(volume size).

Source

pub fn restore_snapshot(&mut self, snap: HfsPlusSnapshot)

Restore previously captured state. Used by the snapshot guard around every EditableFilesystem mutation when the body returns Err.

Source

pub fn prepare_for_edit(&mut self) -> Result<(), FilesystemError>

Sanity-check the volume for edit-mode compatibility and pre-load the attributes-B-tree CNID set. Called by open_editable_filesystem — not by read-only opens, so the cost is paid only when we’re about to mutate.

Journaled volumes are accepted only when the journal is clean (empty, start == end) — the cleanly-unmounted case. A clean journal has no pending state, so editing the catalog in place and leaving the journal empty keeps the volume valid for macOS (it replays nothing on mount). A dirty journal carries metadata the catalog hasn’t absorbed yet; editing on top of that stale catalog would lose the journaled changes, so we still refuse it here. In-place transactional writes that journal our own mutations and recover dirty volumes are the remaining half of Step 27 (docs/hfsplus_enhancements.md) and are not yet wired in.

xattr-bearing volumes succeed but later delete_entry calls against any of the cached CNIDs are refused at the source until Phase 5 lands.

Source§

impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>

Source

pub fn set_xattr( &mut self, cnid: u32, name: &str, value: &[u8], ) -> Result<(), FilesystemError>

Set (or replace) an inline extended attribute on cnid.

Any existing record keyed by (cnid, name) is removed first so the final state holds exactly one record per (cnid, name) pair. Records whose payload would not fit in a single B-tree leaf node are rejected with XattrTooLarge — fork-style storage is not yet wired up.

Wrapped in the snapshot guard so partial failures (e.g. a leaf split running out of free B-tree nodes) leave the in-memory state byte-equal to the pre-call state.

Source

pub fn remove_xattr( &mut self, cnid: u32, name: &str, ) -> Result<(), FilesystemError>

Remove a single extended attribute by name. Returns NotFound if no matching record exists. Wrapped in the snapshot guard.

Source§

impl<R: Read + Write + Seek + Send> HfsPlusFilesystem<R>

Source

pub fn ensure_private_dir(&mut self) -> Result<FileEntry, FilesystemError>

Locate the \0\0\0\0HFS+ Private Data directory at root, creating it if missing. Returns its FileEntry. The directory’s existence is the prerequisite for any hardlink-related write — both Self::create_hardlink_inode and Self::create_hardlink call this internally so the replay path doesn’t need to.

Source

pub fn ensure_dir_private_dir(&mut self) -> Result<FileEntry, FilesystemError>

Locate the .HFS+ Private Directory Data\r directory at root, creating it if missing. Companion to Self::ensure_private_dir for directory hardlinks.

Create a hardlink inode (iNode<inode_num>) under the volume’s private directory. The inode’s data and resource forks hold the shared payload; its bsdInfo.special (linkCount) starts at zero and is bumped per-link by Self::create_hardlink. The caller is responsible for calling sync_metadata once all replay edits are staged.

Returns an AlreadyExists error if iNode<inode_num> already lives in the private dir, so callers don’t accidentally double-emit.

Create a file hardlink stub at (parent, name) referencing the inode iNode<inode_num> under the private dir. The stub carries empty forks and FInfo ('hlnk', 'hfs+') with bsdInfo.special = inode_num. The matching inode’s linkCount (bsdInfo.special) is bumped by 1.

Returns the stub’s FileEntry with link_target_cnid set to the inode’s CNID — callers that want the inode bytes can read through it directly via Self::read_file / Self::write_file_to.

Create a directory-hardlink inode (dir_<inode_num>) under the volume’s directory-private directory. The inode is a regular folder — callers populate its contents like any other directory. Its bsdInfo.special (linkCount) starts at zero and is bumped per stub by Self::create_dir_hardlink.

Create a directory-hardlink stub at (parent, name) referencing dir_<inode_num> under the directory-private directory. The stub is a FILE record (not a folder) with FInfo ('fdrp', 'MACS') and bsdInfo.special = inode_num; list_directory surfaces it as a Directory entry that follows the link automatically. The matching inode’s linkCount (bsdInfo.special) is bumped by 1.

Trait Implementations§

Source§

impl<R: Read + Write + Seek + Send> EditableFilesystem for HfsPlusFilesystem<R>

Source§

fn as_filesystem(&self) -> &dyn Filesystem

Upcast to a read-only Filesystem trait object. Read more
Source§

fn as_filesystem_mut(&mut self) -> &mut dyn Filesystem

Mutable form of as_filesystem.
Source§

fn create_file( &mut self, parent: &FileEntry, name: &str, data: &mut dyn Read, data_len: u64, options: &CreateFileOptions, ) -> Result<FileEntry, FilesystemError>

Create a file in the given parent directory. Read more
Source§

fn create_directory( &mut self, parent: &FileEntry, name: &str, options: &CreateDirectoryOptions, ) -> Result<FileEntry, FilesystemError>

Create a subdirectory in the given parent directory.
Source§

fn delete_entry( &mut self, parent: &FileEntry, entry: &FileEntry, ) -> Result<(), FilesystemError>

Delete a file, empty directory, or symlink. Read more
Source§

fn rename( &mut self, parent: &FileEntry, entry: &FileEntry, new_name: &str, ) -> Result<(), FilesystemError>

Rename an entry within its parent directory, in place: the entry keeps its identity and contents (start cluster / CNID / inode and file data are untouched) — only the name in the parent’s directory listing changes. new_name must validate via Filesystem::validate_name and must not already name another entry in parent. Read more
Source§

fn set_type_creator( &mut self, entry: &FileEntry, type_code: &str, creator_code: &str, ) -> Result<(), FilesystemError>

Set HFS/HFS+ type and creator codes. Override on HFS/HFS+; other filesystems return Unsupported so callers can branch on capability rather than relying on silent no-ops.
Source§

fn write_resource_fork( &mut self, entry: &FileEntry, data: &mut dyn Read, len: u64, ) -> Result<(), FilesystemError>

Write resource fork data. Override on HFS/HFS+; other filesystems return Unsupported rather than silently dropping the fork.
Source§

fn set_permissions( &mut self, entry: &FileEntry, mode: u32, ) -> Result<(), FilesystemError>

Set Unix permission bits on an entry. mode carries permission bits only — implementations keep the inode’s file-type bits (see [crate::fs::unix_common::inode::with_permission_bits]). Read more
Source§

fn set_owner( &mut self, entry: &FileEntry, uid: u32, gid: u32, ) -> Result<(), FilesystemError>

Set the owning uid and gid on an entry. Read more
Source§

fn sync_metadata(&mut self) -> Result<(), FilesystemError>

Flush metadata (superblock, bitmaps, FAT tables, etc.) to disk.
Source§

fn free_space(&mut self) -> Result<u64, FilesystemError>

Returns the number of free bytes available on the filesystem.
Source§

fn set_blessed_folder( &mut self, entry: &FileEntry, ) -> Result<(), FilesystemError>

Mark a folder as the blessed (bootable) system folder. Override on HFS/HFS+; other filesystems return Unsupported so the GUI can gate the action and surface unexpected calls.
Source§

fn write_boot_blocks( &mut self, blocks: &[u8; 1024], ) -> Result<(), FilesystemError>

Stage the 1024-byte HFS boot-block region (sectors 0–1) for the volume, written verbatim at sync_metadata time. Used to make a classic-HFS volume bootable by copying a donor’s boot blocks. Override on HFS/HFS+ (the only filesystems with a Mac boot region); others return Unsupported so the GUI can gate the action.
Source§

fn begin_bulk(&mut self)

Enter “bulk” editing mode for a batch of operations whose atomicity the caller guarantees as a whole (e.g. an importer that discards every change if any single op fails). Implementations may use this to skip per-operation rollback bookkeeping — for filesystems that snapshot the whole metadata on each edit (classic HFS clones its multi-megabyte catalog per create_file), that bookkeeping dominates a large import. Read more
Source§

fn end_bulk(&mut self)

Leave bulk editing mode (see begin_bulk). Default is a no-op.
Source§

fn delete_recursive( &mut self, parent: &FileEntry, entry: &FileEntry, ) -> Result<(), FilesystemError>

Recursively delete a directory and all its contents. Read more
Source§

fn set_xattr( &mut self, _entry: &FileEntry, _name: &str, _value: &[u8], ) -> Result<(), FilesystemError>

Set (create or replace) an extended attribute on an entry. Override on xattr-bearing filesystems (ext / XFS / SquashFS); others return Unsupported. name must carry a valid namespace prefix ([crate::fs::xattr::has_valid_namespace]).
Source§

fn remove_xattr( &mut self, _entry: &FileEntry, _name: &str, ) -> Result<(), FilesystemError>

Remove an extended attribute from an entry. Override on xattr-bearing filesystems; others return Unsupported. Removing an absent attribute is not an error.
Source§

fn set_prodos_type( &mut self, _entry: &FileEntry, _type_byte: u8, _aux_type: u16, ) -> Result<(), FilesystemError>

Set ProDOS file type byte and aux type on an existing file. Default returns Unsupported — override on ProDOS.
Source§

fn set_prodos_access( &mut self, _entry: &FileEntry, _access: u8, ) -> Result<(), FilesystemError>

Set the ProDOS access byte on an existing file or directory. Read more
Source§

fn repair(&mut self) -> Result<RepairReport, FilesystemError>

Attempt to repair filesystem issues found by fsck. Default returns an error indicating repair is not supported.
Source§

fn set_volume_name(&mut self, _new_name: &str) -> Result<(), FilesystemError>

Rename the volume. Override on filesystems where the volume name is stored in metadata that the implementation can rewrite in place (HFS today; others return Unsupported).
Source§

fn set_finder_info( &mut self, _entry: &FileEntry, _finfo: [u8; 16], _fxinfo: [u8; 16], ) -> Result<(), FilesystemError>

Overwrite the full 16-byte FInfo and 16-byte FXInfo Finder metadata on a file’s catalog record. HFS today; others Unsupported. Used by the MacBinary put path to apply Finder flags / location / folder in one shot.
Source§

fn set_amiga_protection( &mut self, _entry: &FileEntry, _protection: u32, ) -> Result<(), FilesystemError>

Overwrite the create/modify/backup dates on a file’s catalog record. Dates are Mac epoch (seconds since 1904-01-01 00:00 UTC). HFS today; others Unsupported. Set the DOS attribute bits on an entry: read-only 0x01, hidden 0x02, system 0x04, archive 0x20. Read more
Source§

fn set_dos_attributes( &mut self, _parent: &FileEntry, _entry: &FileEntry, _attrs: u16, ) -> Result<(), FilesystemError>

Source§

fn set_dates( &mut self, _entry: &FileEntry, _create: u32, _modify: u32, _backup: u32, ) -> Result<(), FilesystemError>

Whether this filesystem can store symbolic links (i.e. Self::create_symlink is actually implemented). Default false; override to true alongside create_symlink. Lets callers (e.g. the tar-import preflight) warn before a lossy import that symlinks will be dropped, rather than discovering it entry-by-entry.
Source§

fn stores_raw_names(&self) -> bool

Whether names are stored as raw bytes via [crate::fs::raw_name] placeholders. Only NeXT UFS volumes say yes; everywhere else a placeholder would land as junk.
Create a symbolic link in parent pointing at target (a path string interpreted by the filesystem’s own resolver semantics). Returns the new symlink’s entry. Default returns Unsupported — override on filesystems that support symlinks (PFS3, AFFS, ext, etc).
Create a hard link in parent pointing at an existing entry already on this filesystem. target must be an entry returned by a previous create_file / create_directory / list_directory call on this same volume — its location identifies the inode / anode / CNID that the new link references. Returns the new hardlink’s entry. Default returns Unsupported — override on filesystems that support hardlinks (PFS3, HFS+, ext, etc).
Source§

impl<R: Read + Seek + Send> Filesystem for HfsPlusFilesystem<R>

Source§

fn allocation_unit(&self) -> Option<u64>

HFS+ allocates fork storage in fixed-size allocation blocks (blockSize), so a fork’s real on-disk footprint is its length rounded up to this unit. Exposing it lets the copy preflight and du model destination consumption exactly instead of falling back to a 512-byte floor.

Source§

fn root(&mut self) -> Result<FileEntry, FilesystemError>

Get the root directory entry.
Source§

fn list_directory( &mut self, entry: &FileEntry, ) -> Result<Vec<FileEntry>, FilesystemError>

List the contents of a directory.
Source§

fn read_file( &mut self, entry: &FileEntry, max_bytes: usize, ) -> Result<Vec<u8>, FilesystemError>

Read file contents (up to max_bytes).
Source§

fn write_file_to( &mut self, entry: &FileEntry, writer: &mut dyn Write, ) -> Result<u64, FilesystemError>

Stream file data to a writer. Returns the number of bytes written. Read more
Source§

fn volume_label(&self) -> Option<&str>

Volume label, if available.
Source§

fn fs_type(&self) -> &str

Filesystem type name (e.g., “FAT16”, “FAT32”).
Source§

fn validate_name(&self, name: &str) -> Result<(), FilesystemError>

Validate that name is legal for a new file or directory on this filesystem. Returns Err(InvalidData) with a human-readable reason when the name violates length, character, or encoding rules. Read more
Source§

fn uses_colon_paths(&self) -> bool

True when this filesystem reserves : as its native path separator (classic Mac HFS / HFS+), so : can never appear in a catalog name. Read more
Source§

fn total_size(&self) -> u64

Total filesystem size in bytes.
Source§

fn used_size(&self) -> u64

Used space in bytes.
Source§

fn last_data_byte(&mut self) -> Result<u64, FilesystemError>

Returns the minimum number of bytes from the partition start needed to capture all filesystem data without moving any data on disk — i.e. the position of the last allocated byte. Used for smart backup trimming. Read more
Source§

fn defragmented_minimum_size(&mut self) -> Result<u64, FilesystemError>

Returns the minimum partition size that could host all live filesystem data after a defragmenting clone (data packed from offset 0). Read more
Source§

fn write_resource_fork_to( &mut self, entry: &FileEntry, writer: &mut dyn Write, ) -> Result<u64, FilesystemError>

Write resource fork data to a writer. Returns the number of bytes written. Default returns Ok(0) (no resource fork).
Source§

fn resource_fork_size(&mut self, entry: &FileEntry) -> u64

Returns the resource fork size for a file entry. Default returns 0.
Source§

fn fragmentation_stats( &mut self, ) -> Option<Result<FragmentationStats, FilesystemError>>

Returns per-fork fragmentation statistics for this volume, or None when the filesystem can’t compute them (e.g. cluster-allocation filesystems whose CompactReader already repacks during the backup stream so “fragmentation” doesn’t translate into a backup-time decision the user can make). Read more
Source§

fn blessed_system_folder(&mut self) -> Option<(u64, String)>

Returns the CNID and name of the blessed (bootable) system folder, if set. Only meaningful for HFS/HFS+.
Source§

fn fsck(&mut self) -> Option<Result<FsckResult, FilesystemError>>

Run filesystem integrity check. Returns None if not supported for this filesystem type. Override in implementations that support fsck.
Source§

fn journal_detail( &mut self, ) -> Result<Option<Box<dyn Any + Send>>, FilesystemError>

Decode the journal into per-transaction detail for a history viewer; Ok(None) if unjournaled. The value is the filesystem’s own detail type (HFS+: JournalDetail), for the caller to downcast.
Source§

fn set_show_deleted(&mut self, _on: bool) -> bool

List deleted-but-readable entries alongside live ones; false when unsupported.
Source§

fn case_insensitive_lookup(&self) -> bool

Whether name lookup falls back to case-insensitive matching, the way the native OS resolves paths on this filesystem; an exact match wins first.
Source§

fn supports_xattrs(&self) -> bool

Whether this filesystem stores POSIX extended attributes (user.*, security.*, …). True on ext / XFS / SquashFS; false on filesystems with no xattr concept (Minix, EFS, FAT, HFS). The UI uses this to show or hide the xattr section rather than probing every file.
Source§

fn list_xattrs( &mut self, _entry: &FileEntry, ) -> Result<Vec<Xattr>, FilesystemError>

Read the extended attributes on entry. Default: none. Override on xattr-bearing filesystems. Returning an empty list is normal — most files carry no xattrs even where the filesystem supports them.

Auto Trait Implementations§

§

impl<R> Freeze for HfsPlusFilesystem<R>
where R: Freeze,

§

impl<R> RefUnwindSafe for HfsPlusFilesystem<R>
where R: RefUnwindSafe,

§

impl<R> Send for HfsPlusFilesystem<R>
where R: Send,

§

impl<R> Sync for HfsPlusFilesystem<R>
where R: Sync,

§

impl<R> Unpin for HfsPlusFilesystem<R>
where R: Unpin,

§

impl<R> UnsafeUnpin for HfsPlusFilesystem<R>
where R: UnsafeUnpin,

§

impl<R> UnwindSafe for HfsPlusFilesystem<R>
where R: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.