Skip to main content

hdf5_pure/
file_create_properties.rs

1//! File-creation properties as one reusable value — the `fcpl` analogue.
2
3use crate::file_space_info::FileSpaceStrategy;
4use crate::libver::LibVer;
5
6/// File-creation properties applied when writing a new HDF5 file.
7///
8/// This is the `hdf5-pure` analogue of an HDF5 **file creation property list**
9/// (`fcpl`): one value carrying every creation-time setting, so application code
10/// can define a file layout once and reuse it everywhere it writes, instead of
11/// repeating a builder call chain and keeping the copies in sync.
12///
13/// The `Properties` suffix means the type stands in for one whole HDF5 property
14/// list, so every setting on it has a C counterpart to look up. It is a stand-in
15/// and not a port: a plain `Copy` value, with no handle to create or close, no
16/// runtime property registry, and no setter that can fail. `fcpl` and each
17/// `H5Pset_*` it models are doc aliases, so a search for either lands here.
18///
19/// One setting crosses the class line. `H5Pset_libver_bounds` is officially a
20/// *file access* property, but this crate checks the bound as the file is
21/// written, so [`with_libver_bounds`](Self::with_libver_bounds) lives here with
22/// the other write-time settings rather than on
23/// [`FileAccessProperties`](crate::FileAccessProperties).
24///
25/// Pass it to [`FileBuilder::with_create_properties`](crate::FileBuilder::with_create_properties)
26/// or [`File::create_with_options`](crate::File::create_with_options). The
27/// equivalent [`FileBuilder`](crate::FileBuilder) methods set the same fields one
28/// at a time and interoperate freely with this.
29///
30/// Values are recorded as given and checked when the file is written, not when
31/// the properties are built — the value is inert data, so an illegal page size
32/// is reported by `finish`/`write` rather than here. Note that a non-paged
33/// userblock size is currently **not** validated against HDF5's power-of-two
34/// rule. [Supported properties](#supported-properties) has the exact coverage.
35///
36/// # Supported properties
37///
38/// HDF5 configures a file through property lists: a file-creation property list (`fcpl`, passed to
39/// `H5Fcreate`), a file-access property list (`fapl`, passed to `H5Fcreate` and `H5Fopen`), and a
40/// dataset-access property list (`dapl`, passed to `H5Dopen`). The crate exposes no property-list
41/// handles, and models all three as plain reusable values:
42/// [`FileCreateProperties`](Self), [`FileAccessProperties`](crate::FileAccessProperties), and
43/// [`DatasetAccessProperties`](crate::DatasetAccessProperties). Build one once and pass it to
44/// [`FileBuilder::with_create_properties`](crate::FileBuilder::with_create_properties) /
45/// [`File::create_with_options`](crate::File::create_with_options), to any `*_with_options` open,
46/// or to [`File::dataset_with_options`](crate::File::dataset_with_options). The equivalent
47/// [`FileBuilder`](crate::FileBuilder) methods set the same creation properties one at a time.
48///
49/// ## The `Properties` suffix
50///
51/// A `*Properties` type models one whole HDF5 property list, so every setting on it is looked up
52/// here, the few with no C counterpart at all included
53/// ([`with_memory_strategy`](crate::FileAccessProperties::with_memory_strategy),
54/// [`with_sync_policy`](crate::FileAccessProperties::with_sync_policy)), which the tables mark as
55/// such. Three types end in that suffix: [`FileCreateProperties`](Self) (`fcpl`),
56/// [`FileAccessProperties`](crate::FileAccessProperties) (`fapl`), and
57/// [`DatasetAccessProperties`](crate::DatasetAccessProperties) (`dapl`). Where a setting belongs to
58/// a different HDF5 class than the table it appears in, that table labels it with its real class in
59/// parentheses.
60///
61/// Modeling a list is not covering it: each of the three models a subset of its list's properties,
62/// and the tables give the whole picture by listing what is unsupported beside what works.
63///
64/// The suffix is a positive claim only. A `*Options` or `*Config` type is silent either way, and
65/// some of them do map to C properties:
66///
67/// - [`ChunkCacheConfig`](crate::ChunkCacheConfig) holds the `rdcc_*` values that both
68///   `H5Pset_cache` (an `fapl` property) and `H5Pset_chunk_cache` (a `dapl` property) take, so it
69///   belongs to no single list.
70/// - [`MetadataCacheConfig`](crate::MetadataCacheConfig) is the analogue of the
71///   `H5AC_cache_config_t` struct that one `fapl` property accepts, and not of a list.
72/// - [`RepackOptions`](crate::RepackOptions),
73///   [`VlenStringReadOptions`](crate::VlenStringReadOptions) and
74///   [`mat::Options`](crate::mat::Options) have no HDF5 counterpart at all.
75///
76/// The `*_with_options` constructors take a different suffix. It marks the
77/// explicit-configuration variant of an open, in the ordinary Rust sense of
78/// [`OpenOptions`](std::fs::OpenOptions), and the type it takes is visible in the signature.
79///
80/// The [file-space strategy guide](crate::_guide::file_space) has the file-space details behind
81/// the table below, and [Editing files in place](crate::_guide::editing) and [Streaming large
82/// files](crate::_guide::streaming) the read-write access modes.
83///
84/// ## Status legend
85///
86/// | Status | Meaning |
87/// |---|---|
88/// | **Genuine** | The property changes the on-disk result exactly as HDF5 specifies, verified against the reference C library. |
89/// | **Recorded** | The value is written to the file and round-trips through the C library, and the layout stays as it was, which is correct for a fresh file, since it has no free space to manage yet. |
90/// | **Read-only** | Honored when a file is read, with no write-side effect. |
91/// | **Behavioral** | Changes how the crate operates on the file, its memory and its durability, without changing a byte of the result. There is nothing on disk to verify against the C library. |
92/// | **Assertion** | Validated on write, and cannot change the emitted format. |
93/// | **Unsupported** | No equivalent: the property is absent, or a file requiring it is rejected up front. |
94/// | **N/A** | Not meaningful for the on-disk format this crate emits. |
95///
96/// ## File-creation properties
97///
98/// Set through [`FileBuilder`](crate::FileBuilder) before [`write`](crate::FileBuilder::write) or
99/// [`finish`](crate::FileBuilder::finish), one at a time or all at once with
100/// [`FileBuilder::with_create_properties`](crate::FileBuilder::with_create_properties).
101/// [`File::create_with_options(path, fcpl, fapl)`](crate::File::create_with_options) applies them
102/// on the owned-handle path, mirroring `H5Fcreate(name, flags, fcpl_id, fapl_id)`. It returns an
103/// open read-write handle, so a creation and access pair the reopen would reject (a paged file
104/// with `persist = false`, or a userblock under
105/// [`MemoryStrategy::Bounded`](crate::MemoryStrategy::Bounded)) fails before anything is written.
106///
107/// | HDF5 property (C API) | `hdf5-pure` | Status | Behavior |
108/// |---|---|---|---|
109/// | `H5Pset_file_space_strategy(PAGE, …)` | [`with_file_space_strategy(FileSpaceStrategy::Page, …)`](Self::with_file_space_strategy) | **Genuine** | Real page-aligned allocation: metadata and raw data occupy separate pages, and each page's free tail is tracked in a per-page-type `FSHD`/`FSSE` manager. The C library reads it as paged and `H5Fget_freespace` matches the tracked total. |
110/// | `H5Pset_file_space_strategy(FSM_AGGR / AGGR / NONE, …)` | `…(FsmAggr / Aggr / None, …)` | **Recorded** | The strategy is stored in the superblock extension, and the layout stays sequential. Freed regions become tracked once a read-write session deletes an object. |
111/// | `persist` flag | 2nd argument of [`with_file_space_strategy`](Self::with_file_space_strategy) | **Genuine** (paged) / **Recorded** (non-paged) | Paged: per-page-type managers are written from creation. Non-paged: the flag records intent, and managers appear after a later delete. |
112/// | `threshold` | 3rd argument of [`with_file_space_strategy`](Self::with_file_space_strategy) | **Recorded (advisory)** | Round-trips through the C library, and the crate tracks every page tail and freed section whatever its value. |
113/// | `H5Pset_file_space_page_size` | [`with_file_space_page_size`](Self::with_file_space_page_size) | **Genuine** (paged) / **Recorded** (non-paged) | Under [`Page`](crate::FileSpaceStrategy::Page) it is the alignment quantum, 4096 by default, and a power of two `>= 512`. Under the other strategies it is recorded and inert. |
114/// | `H5Pset_userblock` | [`with_userblock`](Self::with_userblock) | **Genuine** | Reserves a zero-filled prefix, and every address is base-relative. The HDF5 rule of zero, or a power of two `>= 512`, is validated at write time ([`FormatError::InvalidUserblockSize`](crate::FormatError::InvalidUserblockSize)), since the size is the superblock's base address and a reader scans the doubling sequence alone for the signature. Under [`Page`](crate::FileSpaceStrategy::Page) the userblock must also be a whole number of pages. Its contents come from [`FileBuilder::with_userblock_content`](crate::FileBuilder::with_userblock_content), which every output path emits. |
115/// | `H5Pset_libver_bounds` (`fapl`) | [`with_libver_bounds`](Self::with_libver_bounds) | **Genuine** (1.8 / 1.10) | A format selector between the two the writer emits: `high` picks the v2 (HDF5 1.8) superblock at `Earliest..=V18` and the v3 (1.10) one at anything reaching 1.10, and a lower bound above 1.10 (`V112`, `V114`, `LATEST`) selects the 1.10 format too. As in the C library, the low bound permits newer encodings and requires none. An upper bound older than 1.8, or below the lower bound, is rejected with [`FormatError::LibverBoundsUnsatisfiable`](crate::FormatError::LibverBoundsUnsatisfiable). HDF5 classes this as a file-access property, and it sits here because this crate resolves the bound at write time. |
116/// | `H5Pset_fill_value` / `H5Pset_fill_time` (`dcpl`) | [`DatasetBuilder::with_fill_value`](crate::DatasetBuilder::with_fill_value) | **Genuine** (per dataset) | Encodes the fill value in a v3 Fill Value message, and [`Dataset::fill_value`](crate::Dataset::fill_value) reads it back, from this crate's files and the C library's. |
117/// | `H5Pset_obj_track_times` (`ocpl`) | none | **Unsupported** | Every object is written with times untracked, which is equivalent to `false`, and tracking cannot be enabled. |
118/// | `H5Pset_sym_k` / `H5Pset_istore_k` | none | **N/A** | The v3 superblock omits these fields, and every group is new-style, with link messages and a v2 object header. |
119/// | `H5Pset_link_phase_change` / `H5Pset_est_link_info` (`gcpl`) | none | **Unsupported** | The Group Info message is written minimal, so the C library's defaults apply (max-compact 8, min-dense 6), and the thresholds are not tunable. |
120///
121/// ## Compliance and known limits
122///
123/// The paged and persistent-free-space paths are exercised by C-library crosschecks
124/// (`crates/crosscheck/tests/main/file_space.rs`, `crates/crosscheck/tests/main/bounded_append.rs`): the
125/// reference library recovers the strategy, `H5Fget_freespace` equals the crate's tracked total
126/// exactly, and the C library reopens a paged file read-write and re-paginates it. The crate also
127/// reads and bounded-mutates genuine C-created paged and persisted files. Compliance here means
128/// page alignment and structural validity, and it stops short of reproducing the C allocator's
129/// intra-page packing byte for byte.
130///
131/// The limits that come with it:
132///
133/// - A paged file must persist its free space to be mutated. Both editors grow a paged `persist =
134///   true` file, keeping pages homogeneous and the end of allocation page-aligned. A paged file
135///   created without `persist = true`, or one carrying a userblock, has no usable record of which
136///   pages hold metadata and which hold raw data, so it cannot be grown at all. Recreate it with
137///   `persist = true` and no userblock.
138/// - Free space is under-reported and never over-reported. A final metadata-page tail and the old
139///   bytes of a relocated partial chunk are left untracked, so `H5Fget_freespace` can read slightly
140///   low. The file stays valid.
141/// - `threshold` is advisory, as the table above says.
142/// - File Space Info message version 1 is the only one emitted and read.
143///
144/// [Limitations](crate#limitations) has the full set of deliberate rejections.
145///
146/// # Examples
147///
148/// ```no_run
149/// use hdf5_pure::{FileCreateProperties, FileSpaceStrategy};
150///
151/// // Define the layout once...
152/// fn paged_layout() -> FileCreateProperties {
153///     FileCreateProperties::new()
154///         .with_file_space_strategy(FileSpaceStrategy::Page, true, 1)
155///         .with_file_space_page_size(8192)
156/// }
157///
158/// // ...and reuse it across every write path.
159/// let mut builder = hdf5_pure::FileBuilder::new();
160/// builder.with_create_properties(paged_layout());
161/// builder.create_dataset("data").with_f64_data(&[1.0, 2.0]);
162/// builder.write("out.h5").unwrap();
163/// ```
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165#[doc(alias = "fcpl")]
166pub struct FileCreateProperties {
167    userblock: u64,
168    libver_bounds: Option<(LibVer, LibVer)>,
169    file_space_strategy: Option<(FileSpaceStrategy, bool, u64)>,
170    file_space_page_size: Option<u64>,
171}
172
173impl FileCreateProperties {
174    /// A value carrying the crate's default creation behavior: no userblock,
175    /// no library-version bounds, and the writer's default file-space handling.
176    pub const fn new() -> Self {
177        Self {
178            userblock: 0,
179            libver_bounds: None,
180            file_space_strategy: None,
181            file_space_page_size: None,
182        }
183    }
184
185    /// Reserve a zero-filled userblock of `size` bytes before the superblock.
186    ///
187    /// HDF5 requires a power of two `>= 512`, or 0 for no userblock; the check
188    /// runs when the file is written. See
189    /// [`FileBuilder::with_userblock`](crate::FileBuilder::with_userblock) for how
190    /// to fill the region afterward.
191    #[doc(alias = "H5Pset_userblock")]
192    pub const fn with_userblock(mut self, size: u64) -> Self {
193        self.userblock = size;
194        self
195    }
196
197    /// Constrain the on-disk format version to `[low, high]`.
198    ///
199    /// `high` **selects** the format: `Earliest..=V18` writes the HDF5 1.8 one
200    /// and anything reaching 1.10 writes the 1.10 one, so this changes the bytes
201    /// of every file the properties are applied to. Content the chosen format
202    /// cannot express is refused with
203    /// [`FormatError::LibverTooOldForContent`](crate::FormatError::LibverTooOldForContent)
204    /// rather than silently upgraded — see
205    /// [`FileBuilder::with_libver_bounds`](crate::FileBuilder::with_libver_bounds)
206    /// for which content that is.
207    ///
208    /// `low` only rules formats out, licensing newer encodings without requiring
209    /// them, so a lower bound of `V112`, `V114` or `LATEST` writes the 1.10
210    /// format rather than being refused — provided `high` reaches it. An
211    /// inverted range such as `V114..=V110` is refused with
212    /// [`FormatError::LibverBoundsUnsatisfiable`](crate::FormatError::LibverBoundsUnsatisfiable).
213    ///
214    /// HDF5 classes `H5Pset_libver_bounds` as a *file access* property; it sits
215    /// here because this crate resolves the bound at write time.
216    #[doc(alias = "H5Pset_libver_bounds")]
217    pub const fn with_libver_bounds(mut self, low: LibVer, high: LibVer) -> Self {
218        self.libver_bounds = Some((low, high));
219        self
220    }
221
222    /// Set the file-space management strategy, whether free space persists across
223    /// close, and the smallest free-space section tracked.
224    #[doc(alias = "H5Pset_file_space_strategy")]
225    pub const fn with_file_space_strategy(
226        mut self,
227        strategy: FileSpaceStrategy,
228        persist: bool,
229        threshold: u64,
230    ) -> Self {
231        self.file_space_strategy = Some((strategy, persist, threshold));
232        self
233    }
234
235    /// Set the file-space page size, the allocation quantum under
236    /// [`FileSpaceStrategy::Page`].
237    #[doc(alias = "H5Pset_file_space_page_size")]
238    pub const fn with_file_space_page_size(mut self, page_size: u64) -> Self {
239        self.file_space_page_size = Some(page_size);
240        self
241    }
242
243    /// Return the configured userblock size in bytes (0 for none).
244    pub const fn userblock(&self) -> u64 {
245        self.userblock
246    }
247
248    /// Return the configured library-version bounds, if any.
249    pub const fn libver_bounds(&self) -> Option<(LibVer, LibVer)> {
250        self.libver_bounds
251    }
252
253    /// Return the configured file-space strategy, persist flag, and threshold, if
254    /// any.
255    pub const fn file_space_strategy(&self) -> Option<(FileSpaceStrategy, bool, u64)> {
256        self.file_space_strategy
257    }
258
259    /// Return the configured file-space page size, if any.
260    pub const fn file_space_page_size(&self) -> Option<u64> {
261        self.file_space_page_size
262    }
263}
264
265impl Default for FileCreateProperties {
266    fn default() -> Self {
267        Self::new()
268    }
269}