Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
MountedFilesystem.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#if qStroika_Foundation_Common_Platform_Linux
7#include <fcntl.h>
8#include <poll.h>
9#include <unistd.h>
10#elif qStroika_Foundation_Common_Platform_MacOS
11#include <fstab.h>
12#elif qStroika_Foundation_Common_Platform_Windows
13#include <Windows.h>
14#include <winioctl.h>
15#endif
16
29
30#include "MountedFilesystem.h"
31
32using std::byte;
33
34using namespace Stroika::Foundation;
37using namespace Stroika::Foundation::IO;
39
40// Comment this in to turn on aggressive noisy DbgTrace in this module
41//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
42
43#if qStroika_Foundation_Common_Platform_Linux
44namespace {
45 // This is quirky, and only works for Linux, and /proc/mounts
46 struct Watcher_Proc_Mounts_ {
47 int mfd;
48 Watcher_Proc_Mounts_ (const filesystem::path& fn)
49 : mfd{::open (fn.c_str (), O_RDONLY, 0)}
50 {
51 }
52 ~Watcher_Proc_Mounts_ ()
53 {
54 ::close (mfd);
55 }
56 bool IsNewAvail () const
57 {
58 // according to http://stackoverflow.com/questions/5070801/monitoring-mount-point-changes-via-proc-mounts
59 // have to use poll with POLLPRI | POLLERR flags
60 //
61 // and from https://linux.die.net/man/5/proc
62 // /proc/[pid]/mounts (since Linux 2.4.19)...
63 // Since kernel version 2.6.15, this file is pollable: after opening the file for reading, a change in this file
64 // (i.e., a file system mount or unmount) causes select(2) to mark the file descriptor as readable, and poll(2)
65 // and epoll_wait(2) mark the file as having an error condition.
66 struct pollfd pfd;
67 int rv;
68 pfd.fd = mfd;
69 pfd.events = POLLERR | POLLPRI;
70 pfd.revents = 0;
71 if ((rv = ::poll (&pfd, 1, 0)) >= 0) {
72 if (pfd.revents & POLLERR) {
73 return true;
74 }
75 }
76 return false;
77 }
78 };
79}
80#endif
81
82#if qStroika_Foundation_Common_Platform_MacOS
83namespace {
84 // this also works on Linux, but is a horrible API
86 {
87 // @todo - note - this only appears to capture 'fixed disks' - not network mounts, and and virtual mount points like /dev/
88 KeyedCollection<MountedFilesystemType, filesystem::path> results{[] (const MountedFilesystemType& e) { return e.fMountedOn; }};
89 static mutex sMutex_; // this API (getfsent) is NOT threadsafe, but we can at least make our use re-entrant
90 [[maybe_unused]] lock_guard critSec{sMutex_};
91 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { ::endfsent (); });
92 while (fstab* fs = ::getfsent ()) {
93 results += MountedFilesystemType{fs->fs_file, Containers::Set<filesystem::path>{fs->fs_spec}, String::FromNarrowSDKString (fs->fs_vfstype)};
94 }
95 return results;
96 }
97}
98#endif
99
100namespace {
101 /*
102 * Something like this is used on many unix systems.
103 */
105 {
106 /*
107 * I haven't found this clearly documented yet, but it appears that a filesystem can be over-mounted.
108 * See https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
109 *
110 * So the last one with a given mount point in the file wins.
111 *
112 * EXAMPLE OUTPUT (from ubuntu on WSL):
113 * cat /proc/mounts
114 * /dev/sdb / ext4 rw,relatime,discard,errors=remount-ro,data=ordered 0 0
115 * tmpfs /mnt/wsl tmpfs rw,relatime 0 0
116 * tools /init 9p ro,dirsync,relatime,aname=tools;fmask=022,loose,access=client,trans=fd,rfd=6,wfd=6 0 0
117 * none /dev devtmpfs rw,nosuid,relatime,size=6474068k,nr_inodes=1618517,mode=755 0 0
118 * sysfs /sys sysfs rw,nosuid,nodev,noexec,noatime 0 0
119 * ...
120 */
121 KeyedCollection<MountedFilesystemType, filesystem::path> results{[] (const MountedFilesystemType& e) { return e.fMountedOn; }};
123 for (Sequence<String> line : reader.ReadMatrix (readStream)) {
124#if USE_NOISY_TRACE_IN_THIS_MODULE_
125 DbgTrace ("in IO::FileSystem::{}::ReadMountInfo_MTabLikeFile_ linesize={}, line[0]={}"_f, line.size (), line.empty () ? ""_k : line[0]);
126#endif
127 //
128 // https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s2-proc-mounts.html
129 //
130 // 1 - device name
131 // 2 - mounted on
132 // 3 - fstype
133 //
134 if (line.size () >= 3) {
135 String devName = line[0];
136 // procfs/mounts often contains symbolic links to device files
137 // e.g. /dev/disk/by-uuid/e1d70192-1bb0-461d-b89f-b054e45bfa00
138 if (devName.StartsWith ("/"sv)) {
139 IgnoreExceptionsExceptThreadAbortForCall (devName = String{filesystem::canonical (devName.As<filesystem::path> ())});
140 }
141 filesystem::path mountedAt = line[1].As<filesystem::path> ();
142 String fstype = line[2];
143 static const String kNone_{"none"sv};
144 results.Add (MountedFilesystemType{
145 mountedAt, devName == kNone_ ? Set<filesystem::path>{} : Set<filesystem::path>{devName.As<filesystem::path> ()}, fstype}); // special name none often used when there is no name
146 }
147 }
148 return results;
149 }
150}
151#if qStroika_Foundation_Common_Platform_Linux
152namespace {
154 {
155 // Note - /procfs files always unseekable
156 static const filesystem::path kUseFile2List_{"/proc/mounts"};
157 static const Watcher_Proc_Mounts_ sWatcher_{kUseFile2List_};
159 [] (const MountedFilesystemType& e) { return e.fMountedOn; }};
160 static bool sFirstTime_{true};
161 if (sFirstTime_ or sWatcher_.IsNewAvail ()) {
162 sLastResult_ = ReadMountInfo_MTabLikeFile_ (FileInputStream::New (kUseFile2List_, IO::FileSystem::FileInputStream::eNotSeekable));
163 sFirstTime_ = false;
164 }
165 return sLastResult_;
166 }
167}
168#endif
169#if qStroika_Foundation_Common_Platform_Linux
170namespace {
171 Collection<MountedFilesystemType> ReadMountInfo_ETC_MTAB_ ()
172 {
173 // Note - /procfs files always unseekable and this is sklink to /procfs
174 static const filesystem::path kUseFile2List_{"/etc/mtab"};
175 return ReadMountInfo_MTabLikeFile_ (FileInputStream::New (kUseFile2List_, IO::FileSystem::FileInputStream::eNotSeekable));
176 }
177}
178#endif
179#if qStroika_Foundation_Common_Platform_Windows
180namespace {
181 using DynamicDiskIDType_ = filesystem::path;
182 DynamicDiskIDType_ GetPhysNameForDriveNumber_ (unsigned int i)
183 {
184 // This format is NOT super well documented, and was mostly derived from reading the remarks section
185 // of https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
186 // (DeviceIoControl function)
187 return "\\\\.\\PhysicalDrive{}"_f(i).As<filesystem::path> ();
188 }
189 DISABLE_COMPILER_MSC_WARNING_START (6262) // stack usage OK
190 optional<Set<DynamicDiskIDType_>> GetDisksForVolume_ (String volumeName)
191 {
192 wchar_t volPathsBuf[10 * 1024]; // intentionally uninitialized since we don't use it if GetVolumePathNamesForVolumeNameW () returns error, and its an OUT only parameter
193 DWORD retLen = 0;
195 DWORD x = ::GetVolumePathNamesForVolumeNameW (get<0> (volumeName.c_str (&buf)), volPathsBuf,
196 static_cast<DWORD> (std::size (volPathsBuf)), &retLen);
197 if (x == 0) {
198 return {}; // missing - no known - not empty - answer
199 }
200 else if (retLen <= 1) {
202 }
203 Assert (1 <= Characters::CString::Length (volPathsBuf) and Characters::CString::Length (volPathsBuf) < std::size (volPathsBuf));
204 volumeName = "\\\\.\\"sv + String::FromSDKString (volPathsBuf).SubString (0, -1);
205
206 // @todo - rewrite this - must somehow otherwise callocate this to be large enuf (dynamic alloc) - if we want more disk exents, but not sure when that happens...
207 VOLUME_DISK_EXTENTS volumeDiskExtents;
208 {
209 /*
210 * For reasons I don't understand (maybe a hit at http://superuser.com/questions/733687/give-regular-user-permission-to-access-physical-drive-on-windows)
211 * this only works with admin privileges
212 */
213 HANDLE hHandle = ::CreateFileW (volumeName.As<wstring> ().c_str (), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
214 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
215 if (hHandle == INVALID_HANDLE_VALUE) {
216 return {};
217 }
218 DWORD dwBytesReturned = 0;
219 BOOL bResult = ::DeviceIoControl (hHandle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, nullptr, 0, &volumeDiskExtents,
220 sizeof (volumeDiskExtents), &dwBytesReturned, NULL);
221 ::CloseHandle (hHandle);
222 if (not bResult) {
223 return {};
224 }
225 }
227 for (DWORD n = 0; n < volumeDiskExtents.NumberOfDiskExtents; ++n) {
228 result.Add (GetPhysNameForDriveNumber_ (volumeDiskExtents.Extents[n].DiskNumber));
229 }
230 return result;
231 }
232 DISABLE_COMPILER_MSC_WARNING_END (6262)
233
234 DISABLE_COMPILER_MSC_WARNING_START (6262) // stack usage OK
235 Containers::KeyedCollection<MountedFilesystemType, filesystem::path> GetMountedFilesystems_Windows_ ()
236 {
238 [] (const MountedFilesystemType& e) { return e.fMountedOn; }};
239 TCHAR volumeNameBuf[1024]; // intentionally uninitialized since OUT parameter and not used unless FindFirstVolume success
240
241 HANDLE hVol = INVALID_HANDLE_VALUE;
242 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept {
243 if (hVol != INVALID_HANDLE_VALUE) {
244 ::CloseHandle (hVol);
245 }
246 });
247
248 for (hVol = ::FindFirstVolume (volumeNameBuf, static_cast<DWORD> (std::size (volumeNameBuf))); hVol != INVALID_HANDLE_VALUE;) {
249 DWORD lpMaximumComponentLength;
250 DWORD dwSysFlags;
251 TCHAR fileSysNameBuf[1024];
252 if (::GetVolumeInformation (volumeNameBuf, nullptr, static_cast<DWORD> (std::size (volumeNameBuf)), nullptr, &lpMaximumComponentLength,
253 &dwSysFlags, fileSysNameBuf, static_cast<DWORD> (std::size (fileSysNameBuf)))) {
255 v.fFileSystemType = String::FromSDKString (fileSysNameBuf);
256 v.fVolumeID = String::FromSDKString (volumeNameBuf);
257 v.fDevicePaths = GetDisksForVolume_ (volumeNameBuf);
258
259 TCHAR volPathsBuf[10 * 1024]; // intentionally uninitialized
260 DWORD retLen = 0;
261 DWORD x = ::GetVolumePathNamesForVolumeName (volumeNameBuf, volPathsBuf, static_cast<DWORD> (std::size (volPathsBuf)), &retLen);
262 if (x == 0) {
263 DbgTrace ("Ignoring error getting paths (volume='{}')"_f, String::FromSDKString (volumeNameBuf));
264 }
265 else if (volPathsBuf[0] == 0) {
266 // Ignore - unmounted!
267 DbgTrace ("Ignoring unmounted filesystem (volume='{}')"_f, String::FromSDKString (volumeNameBuf));
268 }
269 else {
270 for (const TCHAR* NameIdx = volPathsBuf; NameIdx[0] != L'\0'; NameIdx += Characters::CString::Length (NameIdx) + 1) {
271 v.fMountedOn = filesystem::path{NameIdx};
272 results.Add (v);
273 }
274 }
275 }
276
277 // find next
278 if (not::FindNextVolume (hVol, volumeNameBuf, static_cast<DWORD> (std::size (volumeNameBuf)))) {
279 ::FindVolumeClose (hVol);
280 hVol = INVALID_HANDLE_VALUE;
281 }
282 }
283 return results;
284 }
285 DISABLE_COMPILER_MSC_WARNING_END (6262)
286}
287#endif
288
289/*
290 ********************************************************************************
291 ******************** IO::FileSystem::MountedFilesystemType *********************
292 ********************************************************************************
293 */
295{
296 StringBuilder sb;
297 sb << "{"sv;
298 sb << "Mounted-On: "sv << fMountedOn;
299 if (fDevicePaths) {
300 sb << ", Device-Paths: "sv << *fDevicePaths;
301 }
302 if (fFileSystemType) {
303 sb << ", FileSystem-Type: '"sv << *fFileSystemType << "'"sv;
304 }
305 if (fVolumeID) {
306 sb << ", Volume-ID: '"sv << *fVolumeID;
307 }
308 sb << "}"sv;
309 return sb;
310}
311
312/*
313 ********************************************************************************
314 ******************** IO::FileSystem::GetMountedFilesystems *********************
315 ********************************************************************************
316 */
318{
319#if qStroika_Foundation_Common_Platform_Linux
320 return ReadMountInfo_FromProcFSMounts_ ();
321#elif qStroika_Foundation_Common_Platform_MacOS
322 return ReadMountInfo_getfsent_ ();
323#elif qStroika_Foundation_Common_Platform_Windows
324 return GetMountedFilesystems_Windows_ ();
325#else
326 // @todo - maybe a start on macos would be to walk the directory /Volumes
328 return {};
329#endif
330}
#define WeakAssertNotImplemented()
Definition Assertions.h:483
#define DbgTrace
Definition Trace.h:309
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1060
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
This COULD be easily used to read CSV files, or tab-delimited files, for example.
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual CONTAINER_OF_T As(CONTAINER_OF_T_CONSTRUCTOR_ARGS... args) const
Containers::KeyedCollection< MountedFilesystemType, filesystem::path > GetMountedFilesystems()
optional< Containers::Set< filesystem::path > > fDevicePaths