stc
Loading...
Searching...
No Matches
Environment.hpp
Go to the documentation of this file.
1
2#pragma once
3
4#include <filesystem>
5#include <optional>
6#include <stdexcept>
7#include <string>
8#include <cstdlib>
9#include <regex>
10#include <array>
11#include <cstdio>
12#include <iostream>
13
14#if !defined(_WIN32)
15#include <unistd.h>
16#include <sys/types.h>
17#include <pwd.h>
18#include <sys/wait.h>
19#else
20#define NOMINMAX
21#include <Windows.h>
22#include <io.h>
23#include <cstring>
24
25#define popen _popen
26#define pclose _pclose
27#define WEXITSTATUS
28#endif
29
30#ifdef __APPLE__
31#include <mach-o/dyld.h>
32#endif
33
34namespace stc {
35
44inline void setEnv(const char* name, const char* value, bool replace = true) {
45#ifndef _WIN32
46 if (value != nullptr) {
47 setenv(name, value, (int) replace);
48 } else {
49 unsetenv(name);
50 }
51#else
52 if (value != nullptr) {
53 _putenv_s(name, value);
54 } else {
55 _putenv_s(name, "");
56 }
57#endif
58}
59
63inline std::string getEnv(const char* name, const std::string& fail = "") {
64#if defined(_WIN32) || defined(_WIN64)
65 char* value = nullptr;
66 size_t len = 0;
67 errno_t err = _dupenv_s(&value, &len, name);
68 if (err != 0 || value == nullptr)
69 return fail;
70
71 return std::string(value);
72#elif defined _GNU_SOURCE
73 // _GNU_SOURCE is predefined with libstdc++ (GCC's stdlib),
74 // and when it's defined, it grants access to secure_getenv.
75 // Dunno if this works outside linux, hence why there's no
76 // forced enabling of _GNU_SOURCE
77 char* output = secure_getenv(name);
78 if (!output)
79 return fail;
80 return std::string(output);
81#else
82#warning "Failed to find a secure getenv() for your OS - please open an issue at https://github.com/LunarWatcher/stc if you know of an equivalent."
83 char* v = std::getenv(name);
84 if (v == nullptr) return fail;
85 // The string may need to be copied here instead, but iDunno
86 // Most operating systems shouldn't be using this anyway, so
87 // it shouldn't be a problem.
88 return std::string(v);
89#endif
90}
91
100inline std::filesystem::path expandUserPath(const std::string& inputPath) {
101 // Convert all backslashes to forward slashes for processing (fuck you Windows)
102 std::string rawPath = std::regex_replace(inputPath, std::regex("\\\\"), "/");
103
104 // In order to universally support paths without doing a hacky if-check elsewhere,
105 // this just returns the path itself if it isn't a home path.
106 // Other paths should work themselves out
107 if (rawPath.at(0) != '~')
108 return rawPath;
109
110 std::optional<std::string> username;
111 std::string remainingPath;
112
113 // The next part of the code is used to parse off the username,
114 // and grab the rest of the path. This is necessary to reconstruct
115 // the path afterwards.
116 std::string mod = rawPath.substr(1);
117 std::vector<std::string> pathSplit; // = StrUtil::splitString(mod, "/", 1);
118
119 // splitString but smaller so I don't have to import everything
120 size_t pos = 0;
121 std::string token, cache;
122 pos = mod.find('/');
123 token = mod.substr(0, pos);
124 cache = mod;
125 cache.erase(0, pos + 1);
126 pathSplit.push_back(token);
127 pathSplit.push_back(cache);
128
129 // Parse off the username
130 if (rawPath.length() >= 2 && rawPath.at(1) != '/') {
131 // ~username
132 // ~username/path
133 username = pathSplit.at(0);
134 remainingPath = pathSplit.at(1);
135 } else if (rawPath.find('/') != std::string::npos) {
136 // The path contains a slash.
137
138 remainingPath = pathSplit.at(1);
139 }
140
141 // We've by now found a username or not. This means we can actually expand
142 // the path.
143
144 std::string homePath = "";
145#if defined(_WIN32) || defined(_WIN64)
146
147 if (!username.has_value()) {
148 auto userProfile = getEnv("USERPROFILE");
149
150 if (userProfile.empty()) {
151 auto homeDrive = getEnv("HOMEDRIVE");
152 if (homeDrive.empty())
153 homeDrive = "";
154
155 auto envHomePath = getEnv("HOMEPATH");
156
157 if (envHomePath.empty()) {
158 throw std::runtime_error("Unable to find %HOMEPATH%. Specify the path explicitly instead.");
159 //return "";
160 }
161 homePath = homeDrive + envHomePath;
162 } else
163 homePath = userProfile;
164
165 } else {
166 throw std::runtime_error("This doesn't work."
167 " Due to Windows having a very limited API for expanding user paths, and it relies on environment "
168 "variables and assumptions, me (the developer), has decided to not implement ~user expansion on "
169 "Windows. "
170 "I cannot easily test it, nor can I find any reassuring information for a universal pattern I can "
171 "use. "
172 "Replace your path with an absolute path instead. An implementation for this feature may be "
173 "available in the future.");
174 //return "";
175 }
176 // Force forward slashes
177 homePath = std::regex_replace(homePath, std::regex("\\\\"), "/");
178
179#else
180 /*
181 The unixes are easier, because they should in theory share a single
182 API that makes this a whole lot easier.
183 The idea is checking for HOME if we're looking for the current user.
184 If we can't find the home variable, fall back to a UNIX-specific^1
185 function that retrieves the path, along with a few other details.
186 see getpwnam(3) for more info on the functions, and passwd(5)
187 for details on the struct. This code drops the HOME variable for
188 various reasons.
189 If a username has been supplied (~username/), getpwnam is used instead
190 of getpwuid. This returns the same type of struct as getpwuid().
191 The bonus with the UNIX approach is that it requires the user to exist,
192 where as Windows for non-existent users with a custom specified path should
193 in theory cause a bad path. This is on the user, however.
194 ^1: untested on mac and other UNIX-based systems. Only verified on Linux.
195 */
196 struct passwd* passwdPtr = nullptr;
197
198 if (!username.has_value()) {
199 // While the home environment variable can be used,
200 // getenv() is considered insecure
201 // secure_getenv is only available on GNU/Linux, and not Mac.
202 // Mac is still compatible with the rest of the code,
203 // so no environment variables are used
204 passwdPtr = getpwuid(getuid());
205 } else {
206 auto& name = *username;
207 passwdPtr = getpwnam(name.c_str());
208 }
209
210 if (passwdPtr == nullptr) {
211 throw std::runtime_error(std::string("Failed to expand the user path for ") + rawPath + ". The system seems to think you don't exist. "
212 "Please specify the path to use - don't abbreviate it with ~.\n");
213 }
214 homePath = passwdPtr->pw_dir;
215#endif
216 return std::filesystem::path{homePath} / remainingPath;
217}
218
224inline std::filesystem::path getHome() {
225
226 std::optional<std::string> username;
227
228 std::string homePath = "";
229#if defined(_WIN32) || defined(_WIN64)
230 auto userProfile = getEnv("USERPROFILE");
231
232 if (userProfile.empty()) {
233 auto homeDrive = getEnv("HOMEDRIVE");
234 if (homeDrive.empty())
235 homeDrive = "";
236
237 auto envHomePath = getEnv("HOMEPATH");
238
239 if (envHomePath.empty()) {
240 throw std::runtime_error("Failed to find home path");
241 }
242 homePath = homeDrive + envHomePath;
243 } else
244 homePath = userProfile;
245
246 // Force forward slashes
247 homePath = std::regex_replace(homePath, std::regex("\\\\"), "/");
248
249#else
250 /*
251 The unixes are easier, because they should in theory share a single
252 API that makes this a whole lot easier.
253 The idea is checking for HOME if we're looking for the current user.
254 If we can't find the home variable, fall back to a UNIX-specific^1
255 function that retrieves the path, along with a few other details.
256 see getpwnam(3) for more info on the functions, and passwd(5)
257 for details on the struct. This code drops the HOME variable for
258 various reasons.
259 If a username has been supplied (~username/), getpwnam is used instead
260 of getpwuid. This returns the same type of struct as getpwuid().
261 The bonus with the UNIX approach is that it requires the user to exist,
262 where as Windows for non-existent users with a custom specified path should
263 in theory cause a bad path. This is on the user, however.
264 ^1: untested on mac and other UNIX-based systems. Only verified on Linux.
265 */
266 struct passwd* passwdPtr = nullptr;
267
268 // While the home environment variable can be used,
269 // getenv() is considered insecure
270 // secure_getenv is only available on GNU/Linux, and not Mac.
271 // Mac is still compatible with the rest of the code,
272 // so no environment variables are used
273 //
274 // The odds that this presents a threat are probably tiny,
275 // but hey, if I'm putting shit in a ton of projects, I'm
276 // gonna make sure I do it properly.
277 //
278 // Also, LOOK AT HOW EASY THIS IS! It's literally 6 lines
279 // of code, and one API call, instead of that if-statement cascade bullshit Windows needs
280 passwdPtr = getpwuid(getuid());
281
282 if (passwdPtr == nullptr) {
283 throw std::runtime_error("Failed to find home directory");
284 }
285 homePath = passwdPtr->pw_dir;
286#endif
287 return std::filesystem::path{homePath};
288}
289
300inline std::string syscommand(const std::string& command, int* codeOutput = nullptr) {
301 std::array<char, 128> buffer;
302 std::string res;
303
304 std::unique_ptr<std::FILE, void(*)(FILE*)> fd {
305 popen(command.c_str(), "r"),
306 [](FILE* fd) {
307 std::ignore = pclose(fd);
308 }
309 };
310 if (!fd) {
311 throw std::runtime_error("Failed to run " + command);
312 }
313 size_t bytes = 0;
314 while ((bytes = fread(
315 buffer.data(),
316 sizeof(buffer[0]),
317 static_cast<int>(buffer.size()),
318 fd.get())
319 ) > 0) {
320 res.insert(res.end(), buffer.begin(), buffer.begin() + bytes);
321 }
322
323 if (codeOutput != nullptr) {
324 int r = pclose(fd.release());
325 int exitCode = WEXITSTATUS(r);
326 *codeOutput = exitCode;
327 }
328 return res;
329}
330
331#ifndef _WIN32
344[[deprecated("Deprecated in favour of a better, fully integrated API. Switch to stc::Unix::Process. This method has vulnerabilities that will not be fixed")]]
345inline std::string syscommand(std::vector<const char*> command, int* codeOutput = nullptr) {
346 command.push_back(nullptr);
347 std::array<char, 256> buffer;
348 std::string res;
349
350
351//#ifndef _WIN32
352 int fd[2];
353 if (pipe(fd) != 0) {
354 throw std::runtime_error("Failed to create pipe");
355 }
356
357 auto pid = fork();
358
359 if (pid < 0) {
360 throw std::runtime_error("Fork error");
361 } else if (pid == 0) {
362 // Child process
363
364 dup2(fd[1], STDOUT_FILENO);
365 dup2(fd[1], STDERR_FILENO);
366 close(fd[0]);
367 close(fd[1]);
368
369 execv(command.at(0), (char**) command.data());
370 exit(1);
371 } else {
372 close(fd[1]);
373 ssize_t bytes = 0;
374 while ((bytes = read(
375 fd[0],
376 buffer.data(), buffer.size()
377 )) > 0) {
378 res.insert(res.end(), buffer.begin(), buffer.begin() + bytes);
379 }
380
381 int status;
382 waitpid(pid, &status, 0);
383
384 if (codeOutput != nullptr) {
385 int exitCode = WEXITSTATUS(status);
386 *codeOutput = exitCode;
387 }
388
389 }
390//#else
391 //using pipe_handle = void*;
392
393 //auto stdoutRead = pipe_handle();
394 //auto stdoutWrite = pipe_handle();
395
396 //auto secAttr = SECURITY_ATTRIBUTES {
397 //.nLength = sizeof(SECURITY_ATTRIBUTES),
398 //.lpSecurityDescriptor = nullptr,
399 //.bInheritHandle = true
400 //};
401
402 //CreatePipe(
403 //&stdoutRead,
404 //&stdoutWrite,
405 //&secAttr,
406 //0
407 //);
408
409 //SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0);
410
411 //auto startInfo = STARTUPINFO {
412 //.cb = sizeof(STARTUPINFOA),
413 //.dwFlags = STARTF_USESTDHANDLES,
414 //.hStdOutput = stdoutWrite,
415 //.hStdError = stdoutWRite
416 //};
417
418
419 //auto procInfo = PROCESS_INFORMATION();
420
421 //CreateProcess(
422 //command.at(0),
423 //nullptr,
424 //nullptr,
425 //nullptr,
426 //true,
427 //0,
428 //nullptr,
429 //nullptr,
430 //&startInfo,
431 //&procInfo
432 //);
433
434 //CloseHandle(stdoutWrite);
435
436 //while(true) {
437 //auto readBytes = DWORD(0);
438
439 //auto success =
440 //ReadFile(
441 //stdoutRead,
442 //buffer.data(),
443 //buffer.size(),
444 //&readBytes,
445 //nullptr
446 //);
447
449 //if(!success) {
450 //break;
451 //}
452
453 //if (readBytes > 0) {
454 //res.insert(res.end(), buffer.begin(), buffer.begin() + readBytes);
455 //}
456
457 //}
458
459 //WaitForSingleObject(procInfo.hProcess, INFINITY);
460 //CloseHandle(stdoutRead);
461
462 //if (codeOutput != nullptr) {
463 //GetExitCodeProcess(procInfo.hProcess, (LPDWORD) codeOutput)
464 //}
465//#endif
466
467 return res;
468}
469
475[[deprecated("Deprecated in favour of a better, fully integrated API. Switch to stc::Unix::Process. This method has vulnerabilities that will not be fixed")]]
477 std::vector<const char*> command,
478 int* codeOutput = nullptr
479) {
480 command.push_back(nullptr);
481
482 auto pid = fork();
483
484 if (pid < 0) {
485 throw std::runtime_error("Fork error");
486 } else if (pid == 0) {
487 execv(command.at(0), (char**) command.data());
488 exit(1);
489 } else {
490 int status;
491 waitpid(pid, &status, 0);
492
493 if (codeOutput != nullptr) {
494 int exitCode = WEXITSTATUS(status);
495 *codeOutput = exitCode;
496 }
497
498 }
499}
500#endif
501
505inline std::optional<std::string> getHostname() {
506 // According to the linux manpage, and the Windows docs page, it looks like approximately 256 bytes is the max
507 // length across all platforms.
508#ifndef _WIN32
509 constexpr size_t size = 256 + 2 /* + 2 for padding, just in case :) */;
510#else
511 constexpr size_t size = MAX_COMPUTERNAME_LENGTH + 1;
512#endif
513
514 char hostname[size];
515#ifndef _WIN32
516 int res = gethostname(hostname, size);
517 if (res != 0) {
518 return std::nullopt;
519 }
520
521 std::string str(hostname);
522 return str;
523#else
524 DWORD _size = size;
525 bool res = GetComputerNameEx(
526 ComputerNamePhysicalDnsHostname,
527 hostname,
528 &_size);
529 if (res == false) {
530 return std::nullopt;
531 }
532 return std::string {
533 hostname,
534 (size_t) _size
535 };
536#endif
537
538}
539
540enum class StreamType {
541 STDOUT,
542 STDERR,
543 OTHER
544};
545
556 if (type == StreamType::OTHER) {
557 return false;
558 }
559 auto streamDescriptor = type == StreamType::STDOUT ? stdout : stderr;
560
561#ifdef _WIN32
562 return _isatty(_fileno(streamDescriptor));
563#else
564 return isatty(fileno(streamDescriptor));
565#endif
566}
567
572inline std::string executablePath() {
573#ifdef __APPLE__
574 const size_t bufSize = PATH_MAX + 1;
575 char dirNameBuffer[bufSize];
576 uint32_t size = bufSize;
577
578 if (_NSGetExecutablePath(dirNameBuffer, &size) != 0) {
579 throw std::runtime_error("Crapple OS strikes again");
580 }
581
582 return std::filesystem::canonical(
583 std::string {
584 dirNameBuffer,
585 size
586 }
587 );
588#elif !defined _WIN32
589 // cannot get over how just convenient and portable this is.
590 // It's literally just a standard library function with a special path
591 return std::filesystem::canonical("/proc/self/exe");
592#else
593 // Then there's _windows_
594 //
595 // Look at all this fucking shit
596 std::vector<char> pathBuf;
597 DWORD copied = 0;
598 do {
599 pathBuf.resize(pathBuf.size() + MAX_PATH);
600 copied = GetModuleFileNameA(0, &pathBuf.at(0), pathBuf.size());
601 } while(copied >= pathBuf.size());
602 pathBuf.resize(copied);
603
604 return std::string {
605 pathBuf.begin(),
606 pathBuf.end()
607 };
608#endif
609}
610
622template <typename CharT>
623static constexpr StreamType getOutputStreamType(std::basic_ostream<CharT>& ss) {
624 if constexpr(std::is_same_v<CharT, char>) {
625 if (&ss == &std::cout) {
626 return StreamType::STDOUT;
627 } else if (&ss == &std::cerr) {
628 return StreamType::STDERR;
629 }
630 } else if constexpr (std::is_same_v<CharT, wchar_t>){
631 if (&ss == &std::wcout) {
632 return StreamType::STDOUT;
633 } else if (&ss == &std::wcerr) {
634 return StreamType::STDERR;
635 }
636 }
637 return StreamType::OTHER;
638}
639
643template <typename CharT>
644inline bool isCppStreamTTY(std::basic_ostream<CharT>& ss) {
645 return isStreamTTY(
647 );
648}
649
650}
Definition CaptureStream.hpp:6
std::string executablePath()
Definition Environment.hpp:572
std::string getEnv(const char *name, const std::string &fail="")
Definition Environment.hpp:63
bool isCppStreamTTY(std::basic_ostream< CharT > &ss)
Definition Environment.hpp:644
void syscommandNoCapture(std::vector< const char * > command, int *codeOutput=nullptr)
Definition Environment.hpp:476
std::optional< std::string > getHostname()
Definition Environment.hpp:505
StreamType
Definition Environment.hpp:540
static constexpr StreamType getOutputStreamType(std::basic_ostream< CharT > &ss)
Definition Environment.hpp:623
std::filesystem::path expandUserPath(const std::string &inputPath)
Definition Environment.hpp:100
std::filesystem::path getHome()
Definition Environment.hpp:224
bool isStreamTTY(StreamType type=StreamType::STDOUT)
Definition Environment.hpp:555
std::string syscommand(const std::string &command, int *codeOutput=nullptr)
Definition Environment.hpp:300
void setEnv(const char *name, const char *value, bool replace=true)
Definition Environment.hpp:44