Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
FileUtils.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cstdio>
7#include <ctime>
8#include <fcntl.h>
9#include <fstream>
10#include <limits>
11#include <sys/stat.h>
12#include <sys/types.h>
13
14#if qStroika_Foundation_Common_Platform_Windows
15#include <aclapi.h>
16#include <io.h>
17#include <shlobj.h>
18#include <windows.h>
19#elif qStroika_Foundation_Common_Platform_POSIX
20#include <unistd.h>
21#endif
22
24#include "Stroika/Foundation/Containers/Set.h"
25#include "Stroika/Foundation/Execution/Exceptions.h"
26#include "Stroika/Foundation/Execution/Throw.h"
27#if qStroika_Foundation_Common_Platform_Windows
28#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
29#include "Stroika/Foundation/Execution/Platform/Windows/HRESULTErrorException.h"
30#endif
31#include "Stroika/Foundation/Containers/Common.h"
37
38#include "FileUtils.h"
39
40using namespace Stroika::Foundation;
43using namespace Stroika::Foundation::Execution;
44using namespace Stroika::Foundation::IO;
46using namespace Stroika::Foundation::Memory;
47
48#if qStroika_Foundation_Common_Platform_Windows
50#endif
51
52/*
53 ********************************************************************************
54 ******************* FileSystem::FileSizeToDisplayString ************************
55 ********************************************************************************
56 */
57String IO::FileSystem::FileSizeToDisplayString (FileOffset_t bytes)
58{
59 if (bytes < 1000) {
60 return Format ("{} bytes"_f, static_cast<int> (bytes));
61 }
62 else if (bytes < 1000 * 1024) {
63 return Format ("%{:.1} K"_f, static_cast<double> (bytes) / 1024.0f);
64 }
65 else {
66 return Format ("{:.1} MB"_f, static_cast<double> (bytes) / (1024 * 1024.0f));
67 }
68}
69
70/*
71 ********************************************************************************
72 ************************ FileSystem::SetFileAccessWideOpened *******************
73 ********************************************************************************
74 */
75/*
76 * Sets permissions for users on a given folder to full control
77 *
78 * Add 'Everyone' to have FULL ACCESS to the given argument file
79 *
80 */
81void IO::FileSystem::SetFileAccessWideOpened (const filesystem::path& filePathName)
82{
83#if qStroika_Foundation_Common_Platform_Windows
84 static PACL pACL = nullptr; // Don't bother with ::LocalFree (pACL); - since we cache keeping this guy around for speed
85 if (pACL == nullptr) {
86 PSID pSIDEveryone = nullptr;
87
88 {
89 // Specify the DACL to use.
90 // Create a SID for the Everyone group.
91 SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
92 if (!::AllocateAndInitializeSid (&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSIDEveryone)) {
93 return; // if this fails - perhaps old OS with no security - just fail silently...
94 }
95 }
96
97 EXPLICIT_ACCESS ea[1]{};
98 // Set FULL access for Everyone.
99 ea[0].grfAccessPermissions = GENERIC_ALL;
100 ea[0].grfAccessMode = SET_ACCESS;
101 ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
102 ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
103 ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
104 ea[0].Trustee.ptstrName = (LPTSTR)pSIDEveryone;
105
106 if (ERROR_SUCCESS != ::SetEntriesInAcl (static_cast<DWORD> (std::size (ea)), ea, nullptr, &pACL)) {
107 ::FreeSid (pSIDEveryone);
108 return; // silently ignore errors - probably just old OS etc....
109 }
110 ::FreeSid (pSIDEveryone);
111 }
112
113 // Try to modify the object's DACL.
114 [[maybe_unused]] DWORD dwRes = ::SetNamedSecurityInfo (const_cast<SDKChar*> (filePathName.c_str ()), // name of the object
115 SE_FILE_OBJECT, // type of object
116 DACL_SECURITY_INFORMATION, // change only the object's DACL
117 nullptr, nullptr, // don't change owner or group
118 pACL, // DACL specified
119 nullptr); // don't change SACL
120 // ignore error from this routine for now - probably means either we don't have permissions or OS too old to support...
121#elif qStroika_Foundation_Common_Platform_POSIX
122 ////TODO: Somewhat PRIMITIVE - TMPHACK
123 if (filePathName.empty ()) [[unlikely]] {
124 Execution::Throw (Exception{make_error_code (errc::no_such_file_or_directory), L"bad filename"_k});
125 }
126 struct stat s;
127 IO::FileSystem::Exception::ThrowPOSIXErrNoIfNegative (::stat (filePathName.generic_string ().c_str (), &s), filePathName);
128
129 mode_t desiredMode = (S_IRUSR | S_IRGRP | S_IROTH) | (S_IWUSR | S_IWGRP | S_IWOTH);
130 if (S_ISDIR (s.st_mode)) {
131 desiredMode |= (S_IXUSR | S_IXGRP | S_IXOTH);
132 }
133
134 int result = 0;
135 // Don't call chmod if mode is already open (because doing so could fail even though we already have what we wnat if were not the owner)
136 if ((s.st_mode & desiredMode) != desiredMode) {
137 result = ::chmod (filePathName.generic_string ().c_str (), desiredMode);
138 }
140#else
142#endif
143}
144
145#if qStroika_Foundation_Common_Platform_Windows
146/*
147 ********************************************************************************
148 ********************** FileSystem::is_cygwin_symlink ***************************
149 ********************************************************************************
150 */
151bool FileSystem::is_cygwin_symlink (const filesystem::path& p)
152{
153 if (filesystem::exists (p)) {
154 DWORD attributes = GetFileAttributes (p.native ().c_str ());
155 if (attributes & FILE_ATTRIBUTE_SYSTEM) [[unlikely]] {
156 try {
157 (void)read_cygwin_symlink (p);
158 return true; // else would throw
159 }
160 catch (...) {
161 return false;
162 }
163 }
164 }
165 return false;
166}
167
168/*
169 ********************************************************************************
170 ********************** FileSystem::read_cygwin_symlink *************************
171 ********************************************************************************
172 */
173filesystem::path FileSystem::read_cygwin_symlink (const filesystem::path& p)
174{
175 if (filesystem::exists (p)) [[likely]] {
176 DWORD attributes = GetFileAttributes (p.native ().c_str ());
177 if (attributes & FILE_ATTRIBUTE_SYSTEM) [[likely]] {
178 ifstream file{p, ios::binary};
179 if (file.is_open ()) [[likely]] {
180 char buffer[MAX_PATH + 11];
181 file.read (buffer, sizeof (buffer));
182 size_t nBytesRead = static_cast<size_t> (file.gcount ());
183 constexpr auto kMagicHeader_ = "!<symlink>"sv;
184 if (nBytesRead > 10 and strncmp (buffer, kMagicHeader_.data (), 10) == 0) [[likely]] {
185 return &buffer[kMagicHeader_.size ()];
186 }
187 }
188 }
189 }
190 Execution::Throw (filesystem_error{"read_cygwin_symlink", p, make_error_code (errc::wrong_protocol_type)}); // not sure best error to throw?
191}
192#endif
193
194/*
195 ********************************************************************************
196 ************************** FileSystem::GetVolumeName ***************************
197 ********************************************************************************
198 */
199String IO::FileSystem::GetVolumeName (const filesystem::path& driveLetterAbsPath)
200{
201#if qStroika_Foundation_Common_Platform_Windows
202 // SEM_FAILCRITICALERRORS needed to avoid dialog in call to GetVolumeInformation
203 AdjustSysErrorMode errorModeAdjuster (AdjustSysErrorMode::GetErrorMode () | SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
204
205 DWORD ignored = 0;
206 SDKChar volNameBuf[1024]{}; // zero init cuz docs (https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa) - not clear on behavior so assume worst
207 SDKChar igBuf[1024]; // dont zero initialize cuz ignored
208 BOOL result = ::GetVolumeInformation (driveLetterAbsPath.c_str (), volNameBuf, static_cast<DWORD> (std::size (volNameBuf)), nullptr,
209 &ignored, &ignored, igBuf, static_cast<DWORD> (std::size (igBuf)));
210 if (result) {
211 return String::FromSDKString (volNameBuf);
212 }
213#else
215#endif
216 return String{};
217}
218
219/*
220 ********************************************************************************
221 ******************************* FileSystem::CopyFile ***************************
222 ********************************************************************************
223 */
224void IO::FileSystem::CopyFile (const filesystem::path& srcFile, const filesystem::path& destPath)
225{
226#if qStroika_Foundation_Common_Platform_Windows
227 // see if can be/should be rewritten to use Win32 API of same name!!!
228 //
229 // If I DON'T do that remapping to Win32 API, then redo this at least to copy / rename through tmpfile
230 IO::FileSystem::Default ().CheckAccess (srcFile, IO::AccessMode::eRead);
231 create_directories (destPath.parent_path ());
232 ThrowIfZeroGetLastError (::CopyFile (destPath.c_str (), srcFile.c_str (), false));
233#else
235#endif
236}
237
238/*
239 ********************************************************************************
240 ***************************** FileSystem::FindFiles ****************************
241 ********************************************************************************
242 */
243vector<String> IO::FileSystem::FindFiles (const filesystem::path& path, const String& fileNameToMatch)
244{
245 vector<String> result;
246 if (path.empty ()) {
247 return result;
248 }
249#if qStroika_Foundation_Common_Platform_Windows
251 String matchFullPath = usePath + (fileNameToMatch.empty () ? L"*" : fileNameToMatch);
252 WIN32_FIND_DATA fd{};
253 HANDLE hFind = ::FindFirstFile (matchFullPath.AsSDKString ().c_str (), &fd);
254 if (hFind != INVALID_HANDLE_VALUE) {
255 try {
256 do {
257 String fileName = String::FromSDKString (fd.cFileName);
258 if (not(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
259 result.push_back (usePath + fileName);
260 }
261 } while (::FindNextFile (hFind, &fd));
262 }
263 catch (...) {
264 ::FindClose (hFind);
265 Execution::ReThrow ();
266 }
267 ::FindClose (hFind);
268 }
269#else
271#endif
272 return result;
273}
274
275/*
276 ********************************************************************************
277 ************************* FileSystem::FindFilesOneDirUnder *********************
278 ********************************************************************************
279 */
280vector<String> IO::FileSystem::FindFilesOneDirUnder (const filesystem::path& path, const String& fileNameToMatch)
281{
282 if (path.empty ()) {
283 return vector<String> ();
284 }
285
286 Containers::Set<String> resultSet;
287#if qStroika_Foundation_Common_Platform_Windows
289 WIN32_FIND_DATA fd{};
290 HANDLE hFind = ::FindFirstFile ((usePath + "*"sv).AsSDKString ().c_str (), &fd);
291 if (hFind != INVALID_HANDLE_VALUE) {
292 do {
293 //SDKString fileName = (LPCTSTR)&fd.cFileName;
294 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
295 String fileName = String::FromSDKString (static_cast<LPCTSTR> (fd.cFileName));
296 static const String kDOT = "."sv;
297 static const String kDOTDOT = ".."sv;
298 if ((fileName != kDOT) and (fileName != kDOTDOT)) {
299 resultSet += Containers::Set<String> (FindFiles (usePath.As<filesystem::path> () / fileName.As<filesystem::path> (), fileNameToMatch));
300 }
301 }
302 } while (::FindNextFile (hFind, &fd));
303 ::FindClose (hFind);
304 }
305#else
307#endif
308 return vector<String>{resultSet.begin (), Iterator<String>{resultSet.end ()}};
309}
310
311#if qStroika_Foundation_Common_Platform_Windows
312/*
313 ********************************************************************************
314 ********************* FileSystem::DirectoryChangeWatcher ***********************
315 ********************************************************************************
316 */
317IO::FileSystem::DirectoryChangeWatcher::DirectoryChangeWatcher (const filesystem::path& directoryName, bool watchSubTree, DWORD notifyFilter)
318 : fDirectory{directoryName}
319 , fWatchSubTree{watchSubTree}
320 , fDoneEvent{::CreateEvent (nullptr, false, false, nullptr)}
321 , fWatchEvent{::FindFirstChangeNotification (fDirectory.AsSDKString ().c_str (), fWatchSubTree, notifyFilter)}
322 , fQuitting{false}
323{
324 fThread = Execution::Thread::New ([this] () { ThreadProc (this); }, Execution::Thread::eAutoStart, "DirectoryChangeWatcher"sv);
325}
326
327IO::FileSystem::DirectoryChangeWatcher::~DirectoryChangeWatcher ()
328{
329 fQuitting = true;
330 if (fDoneEvent != INVALID_HANDLE_VALUE) {
331 Verify (::SetEvent (fDoneEvent));
332 }
333 // critical we wait for finish of thread cuz it has bare 'this' pointer captured
334 Thread::SuppressInterruptionInContext suppressInterruption;
335 IgnoreExceptionsForCall (fThread.AbortAndWaitForDone ());
336 if (fDoneEvent != INVALID_HANDLE_VALUE) {
337 Verify (::CloseHandle (fDoneEvent));
338 }
339 if (fWatchEvent != INVALID_HANDLE_VALUE) {
340 Verify (::FindCloseChangeNotification (fWatchEvent));
341 }
342}
343
344void IO::FileSystem::DirectoryChangeWatcher::ValueChanged ()
345{
346}
347
348void IO::FileSystem::DirectoryChangeWatcher::ThreadProc (void* lpParameter)
349{
350 DirectoryChangeWatcher* _THS_ = reinterpret_cast<DirectoryChangeWatcher*> (lpParameter);
351 while (not _THS_->fQuitting and _THS_->fWatchEvent != INVALID_HANDLE_VALUE) {
352 HANDLE events[2];
353 events[0] = _THS_->fDoneEvent;
354 events[1] = _THS_->fWatchEvent;
355 ::WaitForMultipleObjects (static_cast<DWORD> (std::size (events)), events, false, INFINITE);
356 Verify (::FindNextChangeNotification (_THS_->fWatchEvent));
357 if (not _THS_->fQuitting) {
358 _THS_->ValueChanged ();
359 }
360 }
361}
362#endif
363
364#if qStroika_Foundation_Common_Platform_Windows
365/*
366 ********************************************************************************
367 ********************** FileSystem::AdjustSysErrorMode **************************
368 ********************************************************************************
369 */
370UINT AdjustSysErrorMode::GetErrorMode ()
371{
372 UINT good = ::SetErrorMode (0);
373 ::SetErrorMode (good);
374 return good;
375}
376
377AdjustSysErrorMode::AdjustSysErrorMode (UINT newErrorMode)
378 : fSavedErrorMode{::SetErrorMode (newErrorMode)}
379{
380}
381
382AdjustSysErrorMode::~AdjustSysErrorMode ()
383{
384 (void)::SetErrorMode (fSavedErrorMode);
385}
386#endif
#define AssertNotImplemented()
Definition Assertions.h:402
#define Verify(c)
Definition Assertions.h:420
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
static INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode, const path &p1={}, const path &p2={})
nonvirtual void CheckAccess(const filesystem::path &fileFullPath, AccessMode accessMode=AccessMode::eRead)
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Definition Iterator.h:225
conditional_t< qTargetPlatformSDKUseswchar_t, wchar_t, char > SDKChar
Definition SDKChar.h:71
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:961
String GetVolumeName(const filesystem::path &driveLetterAbsPath)
String AssureDirectoryPathSlashTerminated(const String &dirPath)
Definition PathName.cpp:24