GetEnvironmentVariableW and GetEnvironmentStringsW returns different values - c++

I'm developing an UTF-8 API which should transparently work independent of the OS.
For that I'm replacing getenv by GetEnvironmentVariableW and the char** env passed to main by GetEnvironmentStringsW.
To make sure everything is correct I iterate over the env array and check each value against GetEnvironmentVariableW to make sure they match.
This works everywhere but with MinGW/MSYS where it fails for the env variable temp where getenv = C:\msys64\tmp and GetEnvironmentStringsW returns C:\Users\appveyor\AppData\Local\Temp\1
I verified the value using c:\msys64\usr\bin\env MSYSTEM=MINGW32 c:\msys64\usr\bin\bash -l -c "echo $temp" (similar how the build/test is run) and there it returns C:\Users\appveyor\AppData\Local\Temp\1.
So it seems GetEnvironmentVariableW returns a different/wrong value.
How is this possible? Can I avoid this?
Code of the test is:
for(char** e = env; *e != 0; e++)
{
const char* key_begin = *e;
const char* key_end = strchr(key_begin, '=');
std::string key = std::string(key_begin, key_end);
std::string value = key_end + 1;
const char* env_value = boost::nowide::getenv(key.c_str());
TEST_EQ(env_value, value);
}
where the getenv function is (reduced without error checking etc):
char* getenv(const char* key)
{
static const size_t buf_size = 64;
wchar_t buf[buf_size];
GetEnvironmentVariableW(widen(key), buf, buf_size);
return narrow(buf);
}
Edit: I was able to reproduce this with the following minimal code:
#include <iostream>
#include <windows.h>
#include <string>
int main()
{
const size_t buf_size = 4000;
char buf[buf_size];
GetEnvironmentVariable("temp", buf, buf_size);
std::cout << buf << std::endl;
return 0;
}
When compiled (e.g. in MSVC) and run with temp=Foobar ./a.out in e.g. Git bash on Windows it prints C:\Users\alex\AppData\Local\Temp not Foobar. In GetEnvironmentStrings the value is correct

Related

How to make execvp() handle multiple arguments?

I have a problem with execvp() in C++. Here is my code:
char * argv[]={};
command_counter = 0;
char line[255];
fgets(line,255,stdin);
argv[0] = strtok(line, TOKEN);//seperate the command with TOKEN
while (arg = strtok(NULL, TOKEN)) {
++command_counter;
cout << command_counter << endl;
argv[command_counter] = arg;
cout << argv[command_counter] << endl;
}
argv[++command_counter] = (char *) NULL;
execvp(argv[0],argv);
But the problem is, multiple arguments are not working when I use execvp() like this.
Like ls -a -l, it is only executing the ls -a as a result.
What's wrong with this program?
With the help of you guys the problem was solved by changing the statement of char * argv[128]
The first thing that's wrong with it is that you're creating a zero-sized array to store the arguments:
char * argv[]={};
then populating it.
That's a big undefined behaviour red flag right there.
A quick and dirty fix would be ensuring you have some space there:
char * argv[1000];
but, to be honest, that has its own problems if you ever get to the point where you may have more than a thousand arguments.
Bottom line is, you should ensure there's enough space in the array for storing your arguments.
One way of doing this is with dynamic memory allocation, which expands the array of arguments as needed, so as to ensure there's always enough space:
using namespace std;
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#define TOKEN " "
static char **addArg (char **argv, size_t *pSz, size_t *pUsed, char *str) {
// Make sure enough space for another one.
if (*pUsed == *pSz) {
*pSz = *pSz + 25;
argv = (char **) realloc (argv, *pSz * sizeof (char*));
if (argv == 0) {
cerr << "Out of memory\n";
exit (1);
}
}
// Add it and return (possibly new) array.
argv[(*pUsed)++] = (str == 0) ? 0 : strdup (str);
return argv;
}
int main (void) {
Initial size, used and array.
size_t sz = 0, used = 0;
char **argv = 0;
// Temporary pointer and command.
char *str, line[] = "ls -a -l";
// Add the command itself.
argv = addArg (argv, &sz, &used, strtok (line, TOKEN));
// Add each argument in turn, then the terminator.
while ((str = strtok (0, TOKEN)) != 0)
argv = addArg (argv, &sz, &used, str);
argv = addArg (argv, &sz, &used, 0);
// Then execute it.
execvp (argv[0], argv);
// Shouldn't reach here.
return 0;
}

std::string -> execvp etc

I just found an elusive bug in a program and it turned out to be because with optimization enabled, in something like the following sometimes the std::string is destroyed before processDocument() got the text out of it:
#include <stdio.h>
#include <spawn.h>
#include <string>
static void processDocument(const char* text) {
const char* const argv[] = {
"echo",
text,
NULL,
};
pid_t p;
posix_spawnp(&p, "echo", NULL, NULL, (char**) argv, environ);
}
static int mark = 'A';
static void createDocument() {
const char* vc;
std::string v = "ABCKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK42";
++mark;
v[0] = mark;
vc = v.c_str();
processDocument(vc);
}
int main() {
createDocument();
createDocument();
return(0);
}
How do I safely convert a std::string to a char* for use in execvp, posix_spawnp etc ?
I found out why it really was (here the actual minimal testcase):
std::string resultString;
const char* nodeText;
const char* altText;
resultString = "......whatever1.";
nodeText = resultString.c_str();
resultString = ".....whatever2..";
altText = resultString.c_str();
printf("%s\n", nodeText); // garbage
Bad idea.

How to tell where the application is being ran in C++

I'm using wget along side my app to download a jar and run it. Problem is with my current setup wget.exe would have to be kept in a folder in the app data and that really isn't smart i.e. how would the file get there to begin with?
So how would one find the directory the app is being run in no matter where it is being ran from?
for windows:
std::string calculateRunPath()
{
const unsigned int size = 500;
char buf[size] = {0};
HMODULE hModule = GetModuleHandle(NULL);
GetModuleFileName(hModule,buf, sizeof(buf));
std::string path(buf);
size_t pos = path.find_last_of('\\');
return path.substr(0, pos);
}
for Linux:
std::string calculateRunPath()
{
const unsigned int size = 500;
char path[size + 1] = {0};
size_t len = readlink("/proc/self/exe", path, size);
path[len] = 0;
char* p = strrchr(path, '/');
if(p)
*(p + 1) = 0;
else
path[0] = 0;
return std::string(path);
}
Some boost filesystem goodness should work too, something like...
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <iostream>
int main()
{
std::cout << boost::filesystem::current_path().string() << std::endl;
return 0;
}
You have to read the PWD environment variable

Get parent directory from file in C++

I need to get parent directory from file in C++:
For example:
Input:
D:\Devs\Test\sprite.png
Output:
D:\Devs\Test\ [or D:\Devs\Test]
I can do this with a function:
char *str = "D:\\Devs\\Test\\sprite.png";
for(int i = strlen(str) - 1; i>0; --i)
{
if( str[i] == '\\' )
{
str[i] = '\0';
break;
}
}
But, I just want to know there is exist a built-in function.
I use VC++ 2003.
Thanks in advance.
If you're using std::string instead of a C-style char array, you can use string::find_last_of and string::substr in the following manner:
std::string str = "D:\\Devs\\Test\\sprite.png";
str = str.substr(0, str.find_last_of("/\\"));
Now, with C++17 is possible to use std::filesystem::path::parent_path:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "D:\\Devs\\Test\\sprite.png";
std::cout << "parent of " << p << " is " << p.parent_path() << std::endl;
// parent of "D:\\Devs\\Test\\sprite.png" is "D:\\Devs\\Test"
std::string as_string = p.parent_path().string();
return 0;
}
Heavy duty and cross platform way would be to use boost::filesystem::parent_path(). But obviously this adds overhead you may not desire.
Alternatively you could make use of cstring's strrchr function something like this:
include <cstring>
char * lastSlash = strrchr( str, '\\');
if ( *lastSlash != '\n') *(lastSlash +1) = '\n';
Editing a const string is undefined behavior, so declare something like below:
char str[] = "D:\\Devs\\Test\\sprite.png";
You can use below 1 liner to get your desired result:
*(strrchr(str, '\\') + 1) = 0; // put extra NULL check before if path can have 0 '\' also
On POSIX-compliant systems (*nix) there is a commonly available function for this dirname(3). On windows there is _splitpath.
The _splitpath function breaks a path
into its four components.
void _splitpath(
const char *path,
char *drive,
char *dir,
char *fname,
char *ext
);
So the result (it's what I think you are looking for) would be in dir.
Here's an example:
int main()
{
char *path = "c:\\that\\rainy\\day";
char dir[256];
char drive[8];
errno_t rc;
rc = _splitpath_s(
path, /* the path */
drive, /* drive */
8, /* drive buffer size */
dir, /* dir buffer */
256, /* dir buffer size */
NULL, /* filename */
0, /* filename size */
NULL, /* extension */
0 /* extension size */
);
if (rc != 0) {
cerr << GetLastError();
exit (EXIT_FAILURE);
}
cout << drive << dir << endl;
return EXIT_SUCCESS;
}
On Windows platforms, you can use
PathRemoveFileSpec or PathCchRemoveFileSpec
to achieve this.
However for portability I'd go with the other approaches that are suggested here.
You can use dirname to get the parent directory
Check this link for more info
Raghu

Need help replacing swprintf

I am using some cross platform stuff called nutcracker to go between Windows and Linux, to make a long story short its limited in its support for wide string chars. I have to take the code below and replace what the swprintf is doing and I have no idea how. My experience with low level byte manipulation sucks. Can someone please help me with this?
Please keep in mind I can't go crazy and re-write swprintf but get the basic functionality to format the pwszString correctly from the data in pBuffer. This is c++ using the Microsoft vc6.0 compiler but through CXX so it's limited as well.
The wszSep is just a delimeter, either "" or "-" for readabilty when printing.
HRESULT BufferHelper::Buff2StrASCII(
/*[in]*/ const unsigned char * pBuffer,
/*[in]*/ int iSize,
/*[in]*/ LPWSTR wszSep,
/*[out]*/ LPWSTR* pwszString )
{
// Check args
if (! pwszString) return E_POINTER;
// Allocate memory
int iSep = (int)wcslen(wszSep);
*pwszString = new WCHAR [ (((iSize * ( 2 + iSep )) + 1 ) - iSep ) ];
if (! pwszString) return E_OUTOFMEMORY;
// Loop
int i = 0;
for (i=0; i< iSize; i++)
{
swprintf( (*pwszString)+(i*(2+iSep)), L"%02X%s", pBuffer[i], (i!=(iSize-1)) ? wszSep : L"" );
}
return S_OK;
}
This takes whats in the pBuffer and encodes the wide buffer with ascii. I use typedef const unsigned short* LPCWSTR; because that type does not exist in the nutcracker.
I can post more if you need to see more code.
Thanks.
It is a bit hard to understand exactly what you are looking for, so I've guessed.
As the tag was "C++", not "C" I have converted it to work in a more "C++" way. I don't have a linux box to try this on, but I think it will probably compile OK.
Your description of the input data sounded like UTF-16 wide characters, so I've used a std::wstring for the input buffer. If that is wrong, change it to a std::vector of unsigned chars and adjust the formatting statement accordingly.
#include <string>
#include <vector>
#include <cerrno>
#include <iostream>
#include <iomanip>
#include <sstream>
#if !defined(S_OK)
#define S_OK 0
#define E_OUTOFMEMORY ENOMEM
#endif
unsigned Buff2StrASCII(
const std::wstring &sIn,
const std::wstring &sSep,
std::wstring &sOut)
{
try
{
std::wostringstream os;
for (size_t i=0; i< sIn.size(); i++)
{
if (i)
os << sSep;
os << std::setw(4) << std::setfill(L'0') << std::hex
<< static_cast<unsigned>(sIn[i]);
}
sOut = os.str();
return S_OK;
}
catch (std::bad_alloc &)
{
return E_OUTOFMEMORY;
}
}
int main(int argc, char *argv[])
{
wchar_t szIn[] = L"The quick brown fox";
std::wstring sOut;
Buff2StrASCII(szIn, L" - ", sOut);
std::wcout << sOut << std::endl;
return 0;
}
Why would you use the WSTR types at all in nutcracker / the linux build? Most unixes and linux use utf-8 for their filesystem representation, so, in the non windows builds, you use sprintf, and char*'s.