Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
FileUtils.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. 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> (NEltsOf (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]{};
207 SDKChar igBuf[1024]{};
208 BOOL result = ::GetVolumeInformation (driveLetterAbsPath.c_str (), volNameBuf, static_cast<DWORD> (NEltsOf (volNameBuf)), nullptr,
209 &ignored, &ignored, igBuf, static_cast<DWORD> (NEltsOf (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);
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 memset (&fd, 0, sizeof (fd));
291 HANDLE hFind = ::FindFirstFile ((usePath + L"*").AsSDKString ().c_str (), &fd);
292 if (hFind != INVALID_HANDLE_VALUE) {
293 do {
294 //SDKString fileName = (LPCTSTR)&fd.cFileName;
295 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
296 String fileName = String::FromSDKString ((LPCTSTR)&fd.cFileName);
297 static const String kDOT = "."sv;
298 static const String kDOTDOT = ".."sv;
299 if ((fileName != kDOT) and (fileName != kDOTDOT)) {
300 resultSet += Containers::Set<String> (FindFiles (usePath.As<filesystem::path> () / fileName.As<filesystem::path> (), fileNameToMatch));
301 }
302 }
303 } while (::FindNextFile (hFind, &fd));
304 ::FindClose (hFind);
305 }
306#else
308#endif
309 return vector<String>{resultSet.begin (), Iterator<String>{resultSet.end ()}};
310}
311
312#if qStroika_Foundation_Common_Platform_Windows
313/*
314 ********************************************************************************
315 ********************* FileSystem::DirectoryChangeWatcher ***********************
316 ********************************************************************************
317 */
318IO::FileSystem::DirectoryChangeWatcher::DirectoryChangeWatcher (const filesystem::path& directoryName, bool watchSubTree, DWORD notifyFilter)
319 : fDirectory{directoryName}
320 , fWatchSubTree{watchSubTree}
321 , fDoneEvent{::CreateEvent (nullptr, false, false, nullptr)}
322 , fWatchEvent{::FindFirstChangeNotification (fDirectory.AsSDKString ().c_str (), fWatchSubTree, notifyFilter)}
323 , fQuitting{false}
324{
325 fThread = Execution::Thread::New ([this] () { ThreadProc (this); }, Execution::Thread::eAutoStart, L"DirectoryChangeWatcher");
326}
327
328IO::FileSystem::DirectoryChangeWatcher::~DirectoryChangeWatcher ()
329{
330 fQuitting = true;
331 if (fDoneEvent != INVALID_HANDLE_VALUE) {
332 Verify (::SetEvent (fDoneEvent));
333 }
334 // critical we wait for finish of thread cuz it has bare 'this' pointer captured
335 Thread::SuppressInterruptionInContext suppressInterruption;
336 IgnoreExceptionsForCall (fThread.AbortAndWaitForDone ());
337 if (fDoneEvent != INVALID_HANDLE_VALUE) {
338 Verify (::CloseHandle (fDoneEvent));
339 }
340 if (fWatchEvent != INVALID_HANDLE_VALUE) {
341 Verify (::FindCloseChangeNotification (fWatchEvent));
342 }
343}
344
345void IO::FileSystem::DirectoryChangeWatcher::ValueChanged ()
346{
347}
348
349void IO::FileSystem::DirectoryChangeWatcher::ThreadProc (void* lpParameter)
350{
351 DirectoryChangeWatcher* _THS_ = reinterpret_cast<DirectoryChangeWatcher*> (lpParameter);
352 while (not _THS_->fQuitting and _THS_->fWatchEvent != INVALID_HANDLE_VALUE) {
353 HANDLE events[2];
354 events[0] = _THS_->fDoneEvent;
355 events[1] = _THS_->fWatchEvent;
356 ::WaitForMultipleObjects (static_cast<DWORD> (NEltsOf (events)), events, false, INFINITE);
357 Verify (::FindNextChangeNotification (_THS_->fWatchEvent));
358 if (not _THS_->fQuitting) {
359 _THS_->ValueChanged ();
360 }
361 }
362}
363#endif
364
365#if qStroika_Foundation_Common_Platform_Windows
366/*
367 ********************************************************************************
368 ********************** FileSystem::AdjustSysErrorMode **************************
369 ********************************************************************************
370 */
371UINT AdjustSysErrorMode::GetErrorMode ()
372{
373 UINT good = ::SetErrorMode (0);
374 ::SetErrorMode (good);
375 return good;
376}
377
378AdjustSysErrorMode::AdjustSysErrorMode (UINT newErrorMode)
379 : fSavedErrorMode (::SetErrorMode (newErrorMode))
380{
381}
382
383AdjustSysErrorMode::~AdjustSysErrorMode ()
384{
385 (void)::SetErrorMode (fSavedErrorMode);
386}
387#endif
#define AssertNotImplemented()
Definition Assertions.h:401
#define Verify(c)
Definition Assertions.h:419
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.
Definition Set.h:105
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:955
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
String GetVolumeName(const filesystem::path &driveLetterAbsPath)
String AssureDirectoryPathSlashTerminated(const String &dirPath)
Definition PathName.cpp:24