Skip to main content

VolumeIdentity

Enum VolumeIdentity 

Source
pub enum VolumeIdentity {
    FsUuid([u8; 16]),
    Serial32(u32),
    Serial64(u64),
}
Expand description

A volume’s durable identity, as the platform reports it.

Unlike a mount point or a device node — both of which are session-local and change across remounts, reboots and machines — the value here is stored on the volume itself, so it survives unmounting, re-plugging into another port, and moving the disk to another computer.

The variants differ in strength, and a consumer that builds a registry key from one should keep them apart (for example by prefixing the variant name) rather than mixing their numeric spaces:

  • FsUuid is a 128-bit filesystem UUID and is normally strong enough to stand alone — with one caveat: on the FAT-class filesystems the platform derives the UUID from a narrower serial (see below), so it is no stronger than the value it was derived from, however wide it looks.
  • Serial64 is a 64-bit volume serial. Collisions are unlikely but it carries no structure, so it is only as unique as the formatting tool made it.
  • Serial32 is a 32-bit volume serial — the FAT class. It is weak: 32 bits is small, and some formatting tools derive it from the wall clock, so two volumes formatted in the same second can collide. Consumers that need a durable key should widen it with further invariants (volume size, label, filesystem type).

volume_identity() returns None when the platform or the filesystem genuinely reports no identity at all — a virtual filesystem, a network mount, or a platform without a durable-identity query — or when the platform declines to let this caller look: the volume is no longer there, reading it is not permitted, or the filesystem does not implement the question. None is an honest “nothing to report”. On Apple platforms, Linux and Windows it is never a failure to look: a read that failed for any other reason — no descriptors left, no memory, an I/O error — is returned as the error it is, not as a volume with no identity. On Linux it is also a directory of published names that could not be read whole: one entry declined partway refuses the whole directory, for every device, rather than leave a partial answer standing. On Windows it is also a volume whose file system declined FileFsVolumeInformation through the handle the row is read through.

§The value comes with the assurance of the read

The identity is durable on the volume, but not every platform lets an unprivileged caller read it from the volume: Apple and Windows ask the mounted filesystem, while Linux recovers it from a name udev published about the mount’s source device, which can lag the media now behind that device. So volume_identity() hands back an IdentityReading — this value and the IdentityAssurance it was read at — rather than the value alone, and a caller that must not act on a possibly-lagged name can require Vouched. It is read afresh on every resolve, on every platform: no backend keeps one.

§The same volume answers the same on every platform

The form is fixed per filesystem, not per platform. Whatever a platform can reach is reduced to the one value that filesystem’s volumes are named by, so a disk carried between macOS, Linux and Windows keeps a single key:

FilesystemCanonical identityHow each platform reaches it
APFS, ext2/3/4, XFS, f2fsFsUuid — the UUID in the superblockApple: getattrlist. Linux: the by-uuid name udev’s record of the device lists
btrfsFsUuid — the filesystem’s FSID, one value however many devices carry itLinux: /sys/fs/btrfs/<fsid>/devices/, falling back to /dev/disk/by-uuid
HFS+FsUuid — a version-3 UUID derived from the volume’s 64-bit Finder-info idApple derives it in the kernel; blkid derives the identical value and udev publishes it
exFAT, with no Volume GUIDFsUuid — a version-3 UUID derived from the 32-bit serialApple derives it in the kernel; Linux and Windows compute the same value from the serial they read
exFAT, carrying a Volume GUIDFsUuid — the GUID in the root directory (but see below)Apple only
NTFSSerial64 — the full 64-bit boot-sector serialLinux: the by-uuid name udev’s record lists. Windows: FSCTL_GET_NTFS_VOLUME_DATA
FAT12/16/32Serial32 — the 32-bit boot-sector serial (but see below)Linux: the by-uuid name udev’s record lists. Windows: FileFsVolumeInformation

Four cases cannot be made to agree. Each is a narrowing — a form poorer than the volume’s own identity, never a value invented in its place — and each is recorded here rather than left as a difference a caller would have to discover. In all four the failure is a missed match: two readings of one volume can differ, and no two volumes are made to look alike.

§FAT12/16/32 on Apple platforms

msdosfs never reports the serial. At mount time it derives a version-3 UUID from it and reports only that:

digest    = MD5( b3e20f39-f292-11d6-97a4-00306543ecac, as 16 raw bytes
               ‖ the 4 serial bytes as they sit in the boot sector
               ‖ the BPB total-sector count, as 4 little-endian bytes )
digest[6] = (digest[6] & 0x0f) | 0x30   // version 3
digest[8] = (digest[8] & 0x3f) | 0x80   // RFC 4122 variant

(msdosfs_generate_volume_uuid; the sector count is the BPB’s 16-bit bpbSectors, or its 32-bit bpbHugeSectors when that field is zero.)

The sector count is the obstacle. Nothing unprivileged reports it off Apple: statfs and FileFsFullSizeInformation describe the data area in clusters, while the BPB field also covers the reserved sectors, the FATs and the root directory, so it cannot be recovered from them — and reading the boot sector directly needs a raw volume handle, which needs elevation. Linux and Windows therefore report the narrower Serial32, which does not compare equal to the UUID an Apple platform reports for the same stick. A consumer spanning both should qualify the key with fs_type() and treat the two as separate keyspaces. exFAT’s derivation takes the serial alone, so it is unaffected by the sector count — but see the Volume GUID below.

§NTFS on Windows when the volume FSCTL is unavailable

FileFsVolumeInformation reports only the low 32 bits of the 64-bit serial. The full width comes from FSCTL_GET_NTFS_VOLUME_DATA, asked through the one handle a Windows row is read through; where the file system declines it there, this crate falls back to Serial32 of the low half. That is a truncation of the Serial64 Linux reports for the same volume — the same bits, fewer of them — but the two do not compare equal.

§exFAT volumes carrying a native Volume GUID

The exFAT format permits an optional Volume GUID entry in the root directory, and where one is present it, not the serial, is the volume’s identity: Apple reports that GUID through getattrlist, and exfat.util -k documents the rule exactly — “if the root directory contains a Volume GUID entry, that GUID is the value returned; otherwise, the 32-bit volume serial number stored in the boot sector is converted to a UUID”.

Nothing off Apple can read it. The entry lives in the root directory rather than the boot sector, so reaching it means reading the volume’s data through a raw handle — which needs elevation — and neither FileFsVolumeInformation nor the by-uuid name udev publishes carries it. Linux and Windows therefore report the serial-derived UUID for such a volume, which is a different value from the GUID Apple reports for it. A stamped volume read on two platforms yields two identities; it is never mistaken for another volume.

Stamping is rare — no format tool writes one by default — but it is real: exfat.util -s creates the entry, and one such volume is pinned as a test fixture so this narrowing cannot quietly become untrue.

§exFAT mounted through FUSE

The derivation belongs to the format, so applying it takes proof of the format. Linux gives that proof for the in-kernel driver (exfat) and for a FUSE mount that publishes its subtype (fuse.exfat), and this crate derives the UUID for both. It gives no proof for exfat-fuse mounted as a block-backed FUSE filesystem, which is reported as bare fuseblk — a name shared with ntfs-3g and every other block-backed FUSE helper. There the serial udev published is reported as Serial32 rather than run through a derivation that may not be the volume’s. NTFS is unaffected either way: its identity is the serial itself, whatever the mount publishes as its type.

§Not verified: NTFS on an Apple platform

Apple ships a read-only NTFS driver, and its ntfs.util has a “Get UUID Key” action — so an NTFS volume mounted there may well answer ATTR_VOL_UUID with a UUID, which would be a fifth case of the same kind, since Linux and Windows both name NTFS by its Serial64. No NTFS volume was available to check it against on an Apple host, and an Apple platform is not a place NTFS is usually read, so the row is left unclaimed rather than guessed at in either direction.

§Zero is not an identity

A zero serial and the nil UUID are their formats’ “nothing was ever recorded” sentinels rather than values: every volume that was never given one carries the same zeros, so accepting them would make all of them collide. Every platform maps them to None here. Apple’s kernel already works this way — msdosfs derives no UUID at all from a zero serial.

Variants§

§

FsUuid([u8; 16])

A 128-bit filesystem UUID (APFS, ext2/3/4, XFS, btrfs, f2fs, …), or the version-3 UUID every platform derives for HFS+ and exFAT.

The bytes are in the canonical order of the textual form: the first byte is the one rendered by the leading two hex digits of 8f19a253-d450-3090-abf6-e651943998d1.

§

Serial32(u32)

A 32-bit volume serial — the FAT12/16/32 class, where the on-disk format has no room for a UUID, and the fallback for an NTFS volume whose full serial could not be read. Rendered by most tools as two dash-separated 16-bit halves (1a2b-3c4d); the value here is the whole 32-bit number.

§

Serial64(u64)

A 64-bit volume serial, reported for volumes whose format carries a serial wider than 32 bits but no UUID — NTFS.

Trait Implementations§

Source§

impl Clone for VolumeIdentity

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for VolumeIdentity

Source§

impl Debug for VolumeIdentity

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for VolumeIdentity

The spelling the tools that read these values off a volume print: a UUID in its canonical 8-4-4-4-12 form, a FAT-class serial in the two dash-separated halves blkid and diskutil show it as, and a 64-bit serial as the sixteen hex digits they print for NTFS.

Lowercase throughout, where some tools print the serials uppercase; it is the same value either way, and one crate should spell it one way.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for VolumeIdentity

Source§

impl Hash for VolumeIdentity

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for VolumeIdentity

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for VolumeIdentity

Auto Trait Implementations§

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
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.