Skip to main content

vsc/
store.rs

1//! Store block header, store information, and store attribute flags.
2//!
3//! A store's first block header (record type 0x04) records the size of the
4//! store information that follows it directly. The store information carries the
5//! shadow-copy identity GUIDs, the snapshot context, attribute flags, and the
6//! operating/service machine strings.
7
8use crate::bytes::{le_u16, le_u32, le_u64, read_guid, utf16le_string};
9use crate::guid::{format_guid, VSS_IDENTIFIER};
10
11/// Length of a store block header, in bytes.
12pub const STORE_BLOCK_HEADER_LEN: usize = 128;
13
14/// Record type of a store header block (offset 20).
15pub const STORE_HEADER_RECORD_TYPE: u32 = 0x0004;
16
17/// Upper bound on store-information bytes read, to bound a corrupt size field.
18pub const MAX_STORE_INFO_LEN: usize = 1 << 20;
19
20/// A decoded store block header (the 128-byte record at a store's block offset).
21#[derive(Debug, Clone)]
22pub struct StoreBlockHeader {
23    /// Whether offset 0 carries the VSS identifier GUID.
24    pub has_vss_identifier: bool,
25    /// Format version.
26    pub version: u32,
27    /// Record type (see [`STORE_HEADER_RECORD_TYPE`] and the sibling record
28    /// types 0x0003 block-descriptor-list / 0x0005 ranges / 0x0006 bitmap).
29    pub record_type: u32,
30    /// Store-relative offset of this block.
31    pub relative_offset: u64,
32    /// Volume-relative offset of this block.
33    pub current_offset: u64,
34    /// Volume-relative offset of the next block, or 0 if last.
35    pub next_offset: u64,
36    /// Size of the store information (only meaningful in the first block).
37    pub store_information_size: u64,
38}
39
40impl StoreBlockHeader {
41    /// Parse a 128-byte store block header.
42    #[must_use]
43    pub fn parse(buf: &[u8]) -> Self {
44        StoreBlockHeader {
45            has_vss_identifier: read_guid(buf, 0) == VSS_IDENTIFIER,
46            version: le_u32(buf, 16),
47            record_type: le_u32(buf, 20),
48            relative_offset: le_u64(buf, 24),
49            current_offset: le_u64(buf, 32),
50            next_offset: le_u64(buf, 40),
51            store_information_size: le_u64(buf, 48),
52        }
53    }
54}
55
56/// VSS store attribute flags (`VSS_VOLUME_SNAPSHOT_ATTRIBUTES`).
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct AttributeFlags(pub u32);
59
60impl AttributeFlags {
61    /// Persistent — survives reboot.
62    pub const PERSISTENT: u32 = 0x0000_0001;
63    /// No auto-recovery.
64    pub const NO_AUTO_RECOVERY: u32 = 0x0000_0002;
65    /// Client-accessible (a user-facing "Previous Versions" copy).
66    pub const CLIENT_ACCESSIBLE: u32 = 0x0000_0004;
67    /// No auto-release.
68    pub const NO_AUTO_RELEASE: u32 = 0x0000_0008;
69    /// No writers.
70    pub const NO_WRITERS: u32 = 0x0000_0010;
71    /// Transportable.
72    pub const TRANSPORTABLE: u32 = 0x0000_0020;
73    /// Not surfaced.
74    pub const NOT_SURFACED: u32 = 0x0000_0040;
75    /// Not transacted.
76    pub const NOT_TRANSACTED: u32 = 0x0000_0080;
77    /// Differential — the copy-on-write mechanism.
78    pub const DIFFERENTIAL: u32 = 0x0002_0000;
79    /// Plex — a complete copy.
80    pub const PLEX: u32 = 0x0004_0000;
81
82    /// The raw flag bits.
83    #[must_use]
84    pub fn bits(self) -> u32 {
85        self.0
86    }
87
88    /// Whether any of the given flag bits are set.
89    #[must_use]
90    pub fn contains(self, flag: u32) -> bool {
91        self.0 & flag != 0
92    }
93
94    /// Whether the persistent flag is set (survives reboot).
95    #[must_use]
96    pub fn is_persistent(self) -> bool {
97        self.contains(Self::PERSISTENT)
98    }
99
100    /// Whether the client-accessible flag is set.
101    #[must_use]
102    pub fn is_client_accessible(self) -> bool {
103        self.contains(Self::CLIENT_ACCESSIBLE)
104    }
105
106    /// Whether the differential (copy-on-write) flag is set.
107    #[must_use]
108    pub fn is_differential(self) -> bool {
109        self.contains(Self::DIFFERENTIAL)
110    }
111}
112
113/// The decoded store information record (identity + attributes of a snapshot).
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct StoreInfo {
116    /// Shadow-copy identifier GUID.
117    pub shadow_copy_id: [u8; 16],
118    /// Shadow-copy set identifier GUID.
119    pub shadow_copy_set_id: [u8; 16],
120    /// Snapshot context (backup type).
121    pub snapshot_context: u32,
122    /// Attribute flags.
123    pub attributes: AttributeFlags,
124    /// Operating machine string (UTF-16LE on disk).
125    pub operating_machine: String,
126    /// Service machine string (UTF-16LE on disk).
127    pub service_machine: String,
128}
129
130impl StoreInfo {
131    /// The shadow-copy identifier rendered as a canonical GUID string.
132    #[must_use]
133    pub fn shadow_copy_id_string(&self) -> String {
134        format_guid(&self.shadow_copy_id)
135    }
136
137    /// The shadow-copy set identifier rendered as a canonical GUID string.
138    #[must_use]
139    pub fn shadow_copy_set_id_string(&self) -> String {
140        format_guid(&self.shadow_copy_set_id)
141    }
142
143    /// Parse store information from the bytes directly following the store
144    /// block header.
145    ///
146    /// The two machine strings are length-prefixed UTF-16LE runs; every offset
147    /// and length is bounds-checked, so a truncated or lying length yields an
148    /// empty string rather than a panic or out-of-bounds read.
149    #[must_use]
150    pub fn parse(buf: &[u8]) -> Self {
151        let op_size = le_u16(buf, 64) as usize;
152        let operating_machine = read_string(buf, 66, op_size);
153        let svc_size_off = 66 + op_size;
154        let svc_size = le_u16(buf, svc_size_off) as usize;
155        let service_machine = read_string(buf, svc_size_off + 2, svc_size);
156        StoreInfo {
157            shadow_copy_id: read_guid(buf, 16),
158            shadow_copy_set_id: read_guid(buf, 32),
159            snapshot_context: le_u32(buf, 48),
160            attributes: AttributeFlags(le_u32(buf, 56)),
161            operating_machine,
162            service_machine,
163        }
164    }
165}
166
167/// Read a UTF-16LE string of `len` bytes at `off`, yielding an empty string when
168/// the range is out of bounds.
169fn read_string(buf: &[u8], off: usize, len: usize) -> String {
170    buf.get(off..off + len)
171        .map(utf16le_string)
172        .unwrap_or_default()
173}