From cd101433c5057480dc46b638d403bca2bbf78d4e Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Fri, 15 May 2026 09:36:29 +0200 Subject: [PATCH] macOS: recover mounted volume mount points Prefer hdiutil plist entities that carry a mount-point when recording the virtual device. This fixes APFS images where the first dev-entry is not the mounted volume. Add a macOS mounted-volume refresh hook that recovers VirtualDevice and MountPoint from hdiutil info when FUSE-T SMB auxiliary metadata is missing or stale. --- src/Core/Unix/CoreUnix.cpp | 5 +- src/Core/Unix/CoreUnix.h | 1 + src/Core/Unix/MacOSX/CoreMacOSX.cpp | 129 +++++++++++++++++++++++----- src/Core/Unix/MacOSX/CoreMacOSX.h | 1 + 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/Core/Unix/CoreUnix.cpp b/src/Core/Unix/CoreUnix.cpp index fffd306e..6bcc2fea 100644 --- a/src/Core/Unix/CoreUnix.cpp +++ b/src/Core/Unix/CoreUnix.cpp @@ -594,7 +594,7 @@ namespace VeraCrypt mountedVol->AuxMountPoint = mf.MountPoint; - if (!mountedVol->VirtualDevice.IsEmpty()) + if (mountedVol->MountPoint.IsEmpty() && !mountedVol->VirtualDevice.IsEmpty()) { MountedFilesystemList mpl = GetMountedFilesystems (mountedVol->VirtualDevice); @@ -602,6 +602,9 @@ namespace VeraCrypt mountedVol->MountPoint = mpl.front()->MountPoint; } + if (mountedVol->MountPoint.IsEmpty() || mountedVol->VirtualDevice.IsEmpty()) + UpdateMountedVolumeInfo (mountedVol); + volumes.push_back (mountedVol); if (!volumePath.IsEmpty()) diff --git a/src/Core/Unix/CoreUnix.h b/src/Core/Unix/CoreUnix.h index 79cf4c8e..44681bb6 100644 --- a/src/Core/Unix/CoreUnix.h +++ b/src/Core/Unix/CoreUnix.h @@ -73,6 +73,7 @@ namespace VeraCrypt virtual void MountFilesystem (const DevicePath &devicePath, const DirectoryPath &mountPoint, const string &filesystemType, bool readOnly, const string &systemMountOptions) const; virtual DevicePath MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const; virtual void MountVolumeNative (shared_ptr volume, MountOptions &options, const DirectoryPath &auxMountPoint) const { throw NotApplicable (SRC_POS); } + virtual void UpdateMountedVolumeInfo (shared_ptr mountedVolume) const { (void) mountedVolume; } #ifdef TC_LINUX string DetectFilesystemType (const DevicePath &devicePath) const; #endif diff --git a/src/Core/Unix/MacOSX/CoreMacOSX.cpp b/src/Core/Unix/MacOSX/CoreMacOSX.cpp index a00e10e1..4f46fa69 100644 --- a/src/Core/Unix/MacOSX/CoreMacOSX.cpp +++ b/src/Core/Unix/MacOSX/CoreMacOSX.cpp @@ -77,6 +77,7 @@ namespace VeraCrypt static bool ExtractPlistString (const string &xml, const string &key, size_t start, size_t limit, string &value, size_t *endPos = nullptr) { // hdiutil currently emits simple namevalue pairs. + // This lightweight parser assumes the value follows the requested key. string keyTag = "" + key + ""; size_t p = xml.find (keyTag, start); if (p == string::npos || p >= limit) @@ -124,7 +125,64 @@ namespace VeraCrypt return normalized; } - static DevicePath FindVirtualDeviceByImagePath (const string &imagePath) + static bool ExtractDiskImageDeviceAndMountPoint (const string &xml, size_t start, size_t limit, DevicePath &device, DirectoryPath &mountPoint) + { + string firstDevice; + string mountedDevice; + string mountedPath; + + for (size_t p = start; ; ) + { + size_t devKeyPos = xml.find ("dev-entry", p); + if (devKeyPos == string::npos || devKeyPos >= limit) + break; + + string devEntry; + size_t devValueEnd = 0; + if (!ExtractPlistString (xml, "dev-entry", devKeyPos, limit, devEntry, &devValueEnd)) + { + p = devKeyPos + 1; + continue; + } + + devEntry = StringConverter::Trim (devEntry); + if (firstDevice.empty()) + firstDevice = devEntry; + + size_t nextDevKeyPos = xml.find ("dev-entry", devValueEnd); + if (nextDevKeyPos == string::npos || nextDevKeyPos > limit) + nextDevKeyPos = limit; + + string currentMountPoint; + // hdiutil currently emits dev-entry before mount-point inside each entity. + if (ExtractPlistString (xml, "mount-point", devValueEnd, nextDevKeyPos, currentMountPoint) + && !currentMountPoint.empty()) + { + mountedDevice = devEntry; + mountedPath = currentMountPoint; + break; + } + + p = devValueEnd; + } + + if (!mountedDevice.empty()) + { + device = mountedDevice; + mountPoint = mountedPath; + return true; + } + + if (!firstDevice.empty()) + { + device = firstDevice; + return true; + } + + return false; + } + + static bool FindDiskImageInfoByImagePath (const string &imagePath, DevicePath &device, DirectoryPath &mountPoint) { list args; args.push_back ("info"); @@ -150,15 +208,13 @@ namespace VeraCrypt size_t nextImageKeyPos = xml.find ("image-path", imageValueEnd); if (NormalizeDiskImagePath (currentImagePath) == normalizedImagePath) { - string devEntry; - if (ExtractPlistString (xml, "dev-entry", imageValueEnd, nextImageKeyPos, devEntry)) - return StringConverter::Trim (devEntry); + return ExtractDiskImageDeviceAndMountPoint (xml, imageValueEnd, nextImageKeyPos, device, mountPoint); } p = imageValueEnd; } - return DevicePath(); + return false; } static bool AuxiliaryControlFileHasVirtualDevice (const DirectoryPath &auxMountPoint, const DevicePath &virtualDev, int retryCount = 50) @@ -198,13 +254,11 @@ namespace VeraCrypt shared_ptr CoreMacOSX::DismountVolume (shared_ptr mountedVolume, bool ignoreOpenFiles, bool syncVolumeInfo) { - if (mountedVolume->VirtualDevice.IsEmpty() && !mountedVolume->AuxMountPoint.IsEmpty()) + if (!mountedVolume->AuxMountPoint.IsEmpty()) { try { - DevicePath recoveredVirtualDevice = FindVirtualDeviceByImagePath (string (mountedVolume->AuxMountPoint) + FuseService::GetVolumeImagePath()); - if (!recoveredVirtualDevice.IsEmpty()) - mountedVolume->VirtualDevice = recoveredVirtualDevice; + UpdateMountedVolumeInfo (mountedVolume); } catch (...) { } } @@ -277,6 +331,45 @@ namespace VeraCrypt return mountedVolume; } + void CoreMacOSX::UpdateMountedVolumeInfo (shared_ptr mountedVolume) const + { + if (!mountedVolume || mountedVolume->AuxMountPoint.IsEmpty()) + return; + + try + { + DevicePath recoveredVirtualDevice; + DirectoryPath recoveredMountPoint; + + if (FindDiskImageInfoByImagePath (string (mountedVolume->AuxMountPoint) + FuseService::GetVolumeImagePath(), recoveredVirtualDevice, recoveredMountPoint)) + { + if (!recoveredVirtualDevice.IsEmpty()) + { + if (mountedVolume->VirtualDevice != recoveredVirtualDevice && recoveredMountPoint.IsEmpty()) + mountedVolume->MountPoint = DirectoryPath(); + + mountedVolume->VirtualDevice = recoveredVirtualDevice; + } + + if (!recoveredMountPoint.IsEmpty()) + mountedVolume->MountPoint = recoveredMountPoint; + } + } + catch (...) { } + + if (mountedVolume->MountPoint.IsEmpty() && !mountedVolume->VirtualDevice.IsEmpty()) + { + try + { + MountedFilesystemList mountedFilesystems = GetMountedFilesystems (mountedVolume->VirtualDevice); + + if (mountedFilesystems.size() > 0) + mountedVolume->MountPoint = mountedFilesystems.front()->MountPoint; + } + catch (...) { } + } + } + void CoreMacOSX::CheckFilesystem (shared_ptr mountedVolume, bool repair) const { list args; @@ -378,20 +471,12 @@ namespace VeraCrypt } } - size_t p = xml.find ("dev-entry"); - if (p == string::npos) + DevicePath virtualDev; + DirectoryPath mountPoint; + if (!ExtractDiskImageDeviceAndMountPoint (xml, 0, string::npos, virtualDev, mountPoint) + || virtualDev.IsEmpty()) throw ParameterIncorrect (SRC_POS); - - p = xml.find ("", p); - if (p == string::npos) - throw ParameterIncorrect (SRC_POS); - p += 8; - - size_t e = xml.find ("", p); - if (e == string::npos) - throw ParameterIncorrect (SRC_POS); - - DevicePath virtualDev = StringConverter::Trim (xml.substr (p, e - p)); + (void) mountPoint; try { diff --git a/src/Core/Unix/MacOSX/CoreMacOSX.h b/src/Core/Unix/MacOSX/CoreMacOSX.h index 38ee7237..3d4533f9 100644 --- a/src/Core/Unix/MacOSX/CoreMacOSX.h +++ b/src/Core/Unix/MacOSX/CoreMacOSX.h @@ -30,6 +30,7 @@ namespace VeraCrypt protected: virtual DevicePath MountAuxVolumeImage (const DirectoryPath &auxMountPoint, const MountOptions &options) const; + virtual void UpdateMountedVolumeInfo (shared_ptr mountedVolume) const; private: CoreMacOSX (const CoreMacOSX &);