Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
TemporaryFile.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <fcntl.h>
7#include <sys/stat.h>
8#include <sys/types.h>
9
10#if qStroika_Foundation_Common_Platform_Windows
11#include <io.h>
12#include <windows.h>
13#elif qStroika_Foundation_Common_Platform_POSIX
14#include <unistd.h>
15#endif
16
19#include "Stroika/Foundation/Execution/Common.h"
20#include "Stroika/Foundation/Execution/Exceptions.h"
21#include "Stroika/Foundation/Execution/Module.h"
22#include "Stroika/Foundation/Execution/Process.h"
24
25#include "WellKnownLocations.h"
26
27#include "TemporaryFile.h"
28
29using namespace Stroika::Foundation;
31using namespace Stroika::Foundation::Execution;
32using namespace Stroika::Foundation::IO;
34using namespace Stroika::Foundation::Memory;
35
36/*
37 ********************************************************************************
38 ************************ FileSystem::AppTmpFileManager *************************
39 ********************************************************************************
40 */
41namespace {
42 filesystem::path GetSysTmpRelativePath_ (const AppTmpFileManager::Options& o)
43 {
44 return o.fRelativePathInsideTmpDir.value_or (GetEXEPath ().stem ()); // @todo optimize this use of value_or cuz evaluates args always (sad)
45 }
46}
47
48AppTmpFileManager::AppTmpFileManager (const Options& options)
49{
50 Debug::TraceContextBumper ctx{"AppTmpFileManager::CTOR"};
51 filesystem::path tmpDir = WellKnownLocations::GetTemporary ();
52 filesystem::path cleanedExePath = Execution::GetEXEPath ();
53 filesystem::path exeFileName = cleanedExePath.stem ();
54 // no biggie, but avoid spaces in tmpfile path name (but don't try too hard, should be
55 // harmless)
56 // -- LGP 2009-08-16 // replace any spaces in name with -
57 {
58 auto u = exeFileName.u32string ();
59 for (auto i = u.begin (); i != u.end (); ++i) {
60 if (*i == ' ') {
61 *i = '-';
62 }
63 }
64 exeFileName = u;
65 }
66
67 // Need to include more than just the process-id, since code linked against this lib could be
68 // loaded as a DLL into this process, and it would
69 // use the same dir, and then delete it when that DLL exits (such as the rebranding DLL).
70 //
71 // But whatever we do, the DLL may do also, so we must use what is in the filesystem
72 // to disambiguiate.
73 //
74 tmpDir /= GetSysTmpRelativePath_ (options);
75 DbgTrace ("tmpDir={} (assuring created now...)"_f, tmpDir);
76 try {
77 create_directories (tmpDir);
78 }
79 catch (...) {
80 DbgTrace (L"Error creating tmpdirs, so adjusting and retrying : {}"_f, current_exception ());
81 // tmpDir == GetEXEPath (): happens in regtests - maybe better way to handle -
82 tmpDir.replace_filename (GetEXEPath ().stem ().generic_string () + "-tmpdir");
83 create_directories (tmpDir); // if that doesn't do it, just throw
84 }
85 for (int i = 0; i < INT_MAX; ++i) {
86 filesystem::path trialD =
87 tmpDir / Format ("{}-{}-{}"_f, exeFileName, Execution::GetCurrentProcessID (), i + rand ()).As<filesystem::path> ();
88
89 if (create_directory (trialD) == false) {
90 continue; // try again - DIRECTORY EXISTS
91 }
92 // we succeeded - good! Done... (create_directory throws if cannot create directory)
93 tmpDir = trialD;
94 break;
95 }
96 fTmpDir_ = tmpDir;
97}
98
99AppTmpFileManager::~AppTmpFileManager ()
100{
101 if (not fTmpDir_.empty ()) {
102 DbgTrace (L"AppTmpFileManager::DTOR: clearing {}"_f, fTmpDir_);
103 try {
104 remove_all (fTmpDir_);
105 }
106 catch (...) {
107 DbgTrace ("Ignoring exception clearly AppTmpFileManager files: {}"_f, current_exception ());
108 }
109 }
110}
111
113{
114 DbgTrace ("AppTmpFileManager::DTOR: clearing {}"_f, fTmpDir_);
115 remove_all (fTmpDir_);
116 fTmpDir_ = move (rhs.fTmpDir_); // prevents rhs DTOR from doing anything
117 Assert (rhs.fTmpDir_.empty ()); // cuz of this...
118 return *this;
119}
120
121filesystem::path AppTmpFileManager::GetTmpFile (const String& fileBaseName)
122{
123 filesystem::path fn = GetRootTmpDir ();
124 create_directories (fn);
125 return CreateTmpFile (fileBaseName, fn);
126}
127
128filesystem::path AppTmpFileManager::GetTmpDir (const String& dirNameBase)
129{
130 filesystem::path fn = GetRootTmpDir ();
131 create_directories (fn);
132 for (int attempts = 0; attempts < 5; ++attempts) {
133 char buf[1024];
134 (void)snprintf (buf, NEltsOf (buf), "-%d", ::rand ());
135 filesystem::path trialName = fn / (dirNameBase + buf).As<filesystem::path> ();
136 if (not is_directory (trialName)) {
137 if (create_directories (trialName)) {
138 DbgTrace ("AppTmpFileManager::GetTempDir (): returning '{}'"_f, trialName);
139 WeakAssert (is_directory (trialName)); // possible for someone to have manually deleted, but unlikely
140 return trialName;
141 }
142 }
143 DbgTrace ("Attempt to create directory collided, so retrying ({})"_f, trialName, attempts);
144 }
145 Execution::Throw (Exception{"Unknown error creating temporary directory"sv},
146 "AppTmpFileManager::GetTmpDir (): failed to create tmpdir");
147}
148
149/*
150 ********************************************************************************
151 **************************** FileSystem::ScopedTmpDir **************************
152 ********************************************************************************
153 */
154ScopedTmpDir::ScopedTmpDir (const String& fileNameBase)
155 : fTmpDir_{AppTmpFileManager::sThe.GetTmpDir (fileNameBase)}
156{
157}
158
159ScopedTmpDir::~ScopedTmpDir ()
160{
161 Debug::TraceContextBumper ctx{"ScopedTmpDir::~ScopedTmpDir", "readfilename={}"_f, fTmpDir_};
162 try {
163 remove_all (fTmpDir_);
164 }
165 catch (...) {
166 DbgTrace ("Ignoring exception DTOR: {}"_f, current_exception ());
167 }
168}
169
170/*
171 ********************************************************************************
172 *********************** FileSystem::ScopedTmpFile ******************************
173 ********************************************************************************
174 */
175ScopedTmpFile::ScopedTmpFile (const String& fileBaseName)
176 : fTmpFile_{AppTmpFileManager::sThe.GetTmpFile (fileBaseName)}
177{
178}
179
180ScopedTmpFile::~ScopedTmpFile ()
181{
182 try {
183 remove (fTmpFile_);
184 }
185 catch (...) {
186 DbgTrace ("Ignoring exception clearing file in ScopedTmpFile::~ScopedTmpFile: {}"_f, current_exception ());
187 }
188}
189
190/*
191 ********************************************************************************
192 ************************ FileSystem::CreateTmpFile *****************************
193 ********************************************************************************
194 */
195filesystem::path FileSystem::CreateTmpFile (const String& baseName)
196{
198}
199
200filesystem::path FileSystem::CreateTmpFile (const String& baseName, const filesystem::path& inFolder)
201{
202 filesystem::path baseNamePath = baseName.As<filesystem::path> ();
203 Require (not baseNamePath.has_root_path ());
204 String basename{baseNamePath.stem ()};
205 String ext{baseNamePath.extension ()};
206 if (ext.empty ()) {
207 ext = ".txt"sv;
208 }
209 constexpr int kMaxAttempts_{100};
210 for (int attempts = 0; attempts < kMaxAttempts_; ++attempts) {
211 char buf[1024];
212 (void)snprintf (buf, NEltsOf (buf), "-%d", ::rand ());
213 filesystem::path trialName = inFolder / (basename + buf + ext).As<filesystem::path> ();
214 if (not exists (trialName)) {
215#if qStroika_Foundation_Common_Platform_Windows
216 if (HANDLE fd = ::CreateFile (trialName.native ().c_str (), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
217 nullptr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr);
218 fd != INVALID_HANDLE_VALUE) {
219 ::CloseHandle (fd);
220 DbgTrace ("AppTmpFileManager::GetTmpFile (): returning {}"_f, trialName);
221 WeakAssert (is_regular_file (trialName)); // possible for someone to have manually deleted, but unlikely
222 return trialName;
223 }
224#else
225 if (int fd = ::open (trialName.generic_string ().c_str (), O_RDWR | O_CREAT, filesystem::perms::all); fd >= 0) {
226 close (fd);
227 DbgTrace ("AppTmpFileManager::GetTmpFile (): returning {}"_f, trialName);
228 WeakAssert (is_regular_file (trialName)); // possible for someone to have manually deleted, but unlikely
229 return trialName;
230 }
231#endif
232 }
233 DbgTrace ("Attempt to create file ({}) collided, so retrying ({} attempts)"_f, trialName, attempts);
234 }
235 Execution::Throw (Exception{"Unknown error creating temporary file"sv});
236}
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:438
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
AppTmpFileManager & operator=(AppTmpFileManager &&rhs) noexcept
nonvirtual filesystem::path GetTmpFile(const String &fileBaseName)
AppTmpFileManager(const AppTmpFileManager &)=delete
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
filesystem::path GetEXEPath()
Definition Module.cpp:53
filesystem::path CreateTmpFile(const String &baseName)