C++ write to file error with full PATH from GetEnvironmentVariable() - c++

I'm noob in C++ but wanting to learn. I have a little program that writes some info to my \etc\hosts in Windows; I get the %WINDIR% variable via GetEnvironmentVariable(), if I put the full path manually everything is ok, but when I substitute with WINDIR variable my code isn't compiling. I know I don't do something right.
#include <windows.h>
#include <ios>
#include <fstream>
char buffer[1000];
int main() {
GetEnvironmentVariable("WINDIR",(char*)&buffer,sizeof(buffer));
std::ofstream log;
log.open("%s\\system32\\drivers\\etc\\hosts", buffer);
log << "127.0.0.1 domain.com\n" << std::endl;
return 0;
}
I get really ugly errors like:
C:\Documents and Settings\xtmtrx\Desktop\coding\windir.cpp no matching function for call to `std::basic_ofstream<char, std::char_traits<char> >::open(const char[30], char[1000])'

ofstream cannot format the path for you. You need to do that separately, eg:
#include <windows.h>
#include <ios>
#include <fstream>
char buffer[1000] = {0};
int main() {
GetEnvironmentVariable("WINDIR",buffer,sizeof(buffer));
strcat(buffer, "\\system32\\drivers\\etc\\hosts");
std::ofstream log;
log.open(buffer, ios_base::ate);
log << "127.0.0.1 domain.com\n" << std::endl;
return 0;
}
FYI, you should use GetWindowsDirectory(), GetSystemDirectory(), SHGetSpecialFolderPath() or SHGetKnownFolderPath() instead of GetEnvironmentVariable(). And you should use PathCombine() when concantenating paths together so it can ensure the slashes are correct.

open("%s\\system32\\drivers\\etc\\hosts", buffer); open doesn't understand format strings..you are using %s does not make sense. learn here
Try like this:
GetEnvironmentVariable("WINDIR",buffer,sizeof(buffer));
strcat(buffer, "\\system32\\drivers\\etc\\hosts");
std::ofstream log;
log.open(buffer.str().c_str(), ios_base::ate);

You need to concate the string together like this:
LPTSTR windir[MAX_PATH];
LPTSTR fullpath[MAX_PATH];
GetWindowsDirectory(windir, MAX_PATH);
if(PathCombine(fullpath, windir, _T("system32\\drivers\\etc\\hosts")) != NULL) {
std::ofstream log;
log.open(buffer, ios_base::ate);
log << "127.0.0.1 domain.com\n" << std::endl;
}
At first you need to concate the directory and the file part with PathCombine. Then you can open the file and write the content. You should also note that you need admin permissions to change this file and some antivirus programmes may reject the access of the hosts file.

Related

fwrite() writes garbage at the end

I'm trying to write a function that execute a sql file with postgres. Postgres rise me an exception but without specificate the error. So I tryed to rewrite what it read, and I discovery that the file has some garbage at end
stat("treebase.sql",&buf);
dbschema= new (std::nothrow) char[buf.st_size+1];
if(!dbschema)
{
wxMessageBox(_("Not Enough memory"));
return;
}
if( !(fl=fopen("treebase.sql","r")))
{
wxMessageBox(_("Can not open treebase.sql"));
delete []dbschema;
return;
};
fo=fopen("tbout.sql","w");
fread(dbschema,sizeof(char),buf.st_size,fl);
fclose(fl);
dbschema[buf.st_size]='\0';
fwrite(dbschema,sizeof(char),buf.st_size+1,fo);
fflush(fo);
fclose(fo);
and the result is
![screen shot][1]
The input file 150473 length, the output is 156010. I really can not undersand where the 5000 bytes come from.
where is the bug?
[1]: https://i.stack.imgur.com/IXesz.png
You probably can't read buf.st_size much of data, because of the mode of fopen is "r" which defaults to text modes. In text mode fread and fwrite may do some conversions on what you read or write to match the environment special rules about text files such as end of lines. Use "rb" and "wb" modes for fopen for reading and writing binary files as is respectively.
Also, I would rather use fseek and ftell to get the size of file instead of stat.
Here's an example of how you could read the content of the file into memory and then write down an exact copy to another file. I added error checking too to make it clear if anything goes wrong. There's also no need to use stat etc. Plain standard C++ will do.
#include <cerrno>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <string>
std::string get_file_as_string(const std::string& filename) {
std::ifstream fl(filename, std::ios::binary); // binary mode
if(!fl) throw std::runtime_error(std::strerror(errno));
// return the content of the whole file as a std::string
return {std::istreambuf_iterator<char>(fl),
std::istreambuf_iterator<char>{}};
}
bool write_string_to_file(const std::string& str, const std::string& filename) {
std::ofstream fo(filename, std::ios::binary);
if(!fo) return false;
// return true or false depending on if it succeeded writing the file:
return static_cast<bool>(fo << str);
}
int main() {
auto dbschema = get_file_as_string("treebase.sql");
// use dbschema.c_str() if you need a `const char*`:
postgres_c_function(dbschema.c_str());
// use dbschema.data() if you need a `char*`:
postgres_c_function(dbschema.data());
if(write_string_to_file(dbschema, "tbout.sql")) {
std::cout << "success\n";
} else {
std::cout << "failure: " << std::strerror(errno) << '\n';
}
}

How to create folder in %appdata%, create .bat file in it, and then execute it?

I'm creating C++ code that will create some .bat file and store it in the %appdata% folder. I've successfully to created the file, but still fail to create the folder and execute it.
Below is my simple code, it doesn't look simple but it works to create .bat file in %appdata%, maybe someone can help me to find the simple one.
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <sstream>
#include <string>
#include <windows.h>
#include <direct.h>
int main(int argc, char **argv) {
using namespace std;
std::ofstream aaa;
ostringstream aaa;
aaa.open(aaa1.str());
aaa1 << getenv("appdata") << "/"
<< "test.bat";
aaa.open(aaa1.str());
Updater << "#echo on" << endl;
Updater << "echo \"on\"" << endl;
return 0;
}
The code successfully creates the .bat file in %appdata%, but I need to store in new folder in %appdata%, say New Folder, and then execute the .bat file.
Create Directory
1st get the path using _dupenv_s() in string add new folder name "\New Folder"
2nd Create Directory using _mkdir(str.c_str());
3rd Create "test.bat" using std::ofstream outf(str);
#include "stdafx.h"
#include<fstream>
#include<iostream>
#include<conio.h>
#include<direct.h>
using std::cout;
using std::cin;
using std::endl;
int tmain(int argc, TCHAR* argv[])
{
char *pValue;
size_t len;
errno_t err = _dupenv_s(&pValue, &len, "APPDATA");
std::string NewFile = "\\new";
std::string str(pValue);
str = str + NewFile;
_mkdir(str.c_str());
str = str + "\\Sample.bat"; //
std::ofstream outf(str);
if (!outf)
{
printf("error ");
}
outf << "this is line1" << endl;
outf << "line 2" << endl;
return 0;
}
Please! Don't Forgot to Vote If its Helps
Creating/running an executable in a user writable location is something to be careful with (exploit people into running your process elevated, then running an attack payload), otherwise just a couple of things to tie together.
On Windows, most of those environment variables exist for legacy / compatibility reasons, SHGetKnownFolderPath is the modern way to find the folders. It allocates enough space for the path, be careful with manual memory from C-API, get it a unique_ptr or wstring as soon as possible. It works from Vista, there are older API's if really needed.
wchar_t *str = nullptr;
SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, NULL, &str); // CHECK RETURN
...use str...
CoTaskMemFree(str);
Also be aware of Unicode and spaces in file paths.
Processes have two options, there is the system(command_line) in the cstdlib header, or for advanced use check out the Windows CreateProcessW. Something like:
STARTTUPINFO startup;
startup.cb = sizeof(startup);
PROCESS_INFORMATION pi;
CreateProcessW(NULL, L"cmd.exe /C C:\\ThePath\\myfile.bat", NULL, NULL, FALSE, 0, NULL, NULL, &startup, &pi);
Obviously specific to Windows. Linux, Mac, etc. have their own filesystem layouts and security.
C++ fstream won't create directories for you automatically. You could set up such directories as part of an installer, but to do it at runtime C++17 has std::filesystem::create_directories, which takes a path. If you can't use C++17, use CreateDirectory or _mkdir. Again on Windows be aware of Unicode.

Always output to screen and allow redirection

I'm writing a small CLI application and I want to allow the user to redirect to a file while standard cout statements go to the output.txt file I want progress to always to go the screen.
./myApp > output.txt
10% complete
...
90% complete
Completed
Is this possible? How can I do it?
Thanks in advance!!
This will work even if both stdin and stdout have been redirected:
spectras#etherbee:~$ ./term
hello terminal!
spectras#etherbee:~$ ./term >/dev/null 2>&1
hello terminal!
The idea is to open the controlling terminal of the process directly, bypassing any redirection, like this:
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("/dev/tty", O_WRONLY);
if (fd < 0 && errno != ENODEV) {
/* something went wrong */
return 1;
}
int hasTTY = (fd >= 0);
if (hasTTY) {
write(fd, "hello terminal!\n", 16);
}
return 0;
}
From man 4 tty:
The file /dev/tty is a character file with major number 5 and
minor number 0, usually of mode 0666 and owner.group root.tty. It is
a synonym for the controlling terminal of a process, if any.
If you're using C++, you might want to wrap the file descriptor into a custom streambuf, so you can use regular stream API on it. Alternately, some implementations of the C++ library offer extensions for that purpose. See here.
Or, if you don't care about getting the error code reliably, you could just std::ofstream terminal("/dev/tty").
Also as a design consideration if you do this, offering a quiet option to let the user turn off the writing to the terminal is a good idea.
Your process cannot know if the shell redirects the standard console output (std::cout) or not.
So you'll need another handle that lets you output to the terminal independently of that redirection.
As #Mark mentioned in their comment you could (ab-)use1 std::cerr to do that, along with some ASCII trickery to overwrite the current output line at the terminal (look at backspace characters: '\b').
1)Not to mention the mess printed at the terminal if the output isn't actually redirected.
You can write your progress indicators to the stderr stream. They will appear on the console if the user redirects stdout to a file.
For example:
fprintf(stderr, "10%% complete\n");
I figured out how to do it, even if the user redirects stderr. The following code gets the name of the current terminal and checks to see if our output is being redirected. It also has a my_write() function that allows you to write to both the terminal and the redirect file, if they've redirected stdout. You can use the my_write() function with the writetoterm variable where-ever you want to write something that you want to always be written to the terminal. The extern "C" has to be there, otherwise (on Debian 9 with GCC 6.3, anyway) the ttyname() function will just return NULL all the time.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sstream>
using std::string;
using std::fstream;
using std::cout;
using std::endl;
using std::cerr;
using std::stringstream;
void my_write(bool writetoterm, int termfd, string data)
{
if(writetoterm)
{
int result = write(termfd, data.c_str(), data.length());
if(result < data.length()){
cerr << "Error writing data to tty" << endl;
}
}
cout << data;
}
extern "C" {
char* GetTTY(int fd){
//printf("%s", ttyname(fd));
return ttyname(fd);
}
}
int main(int argc, char** argv){
getenv("TTY");
bool writetoterm = false;
struct stat sb = {};
if(!GetTTY(STDOUT_FILENO)){
//not a TTY
writetoterm = true;
}
int ttyfd = open(GetTTY(2), O_WRONLY);
if(ttyfd < 0){
//error in opening
cout << strerror(errno) << endl;
}
string data = "Hello, world!\n";
my_write(true, ttyfd, data);
int num_for_cout = 42;
stringstream ss;
ss << "If you need to use cout to send something that's not a string" << endl;
ss << "Do this: " << num_for_cout << endl;
my_write(writetoterm, ttyfd, ss.str().c_str());
return 0;
}
I found the official std:: method of handling this. There is another type... std::clog. This is specifically for information and always appears on the command line even though the user redirects the output of the program myProgram > out.txt.
Thanks this was great to see all the methods that this can be done.

Cannot open file with relative path? (C++ ifstream)

I know this seems like a simple question, but I tried everything I can think of to no avail to something that shouldn't have been a problem in the first place.
This is a small C++ program that opens a file. When I open it with its absolute filepath, it works fine. With a relative path, however, it stops working.
Here's the file path of the program and the files I'm trying to read:
C++ program: "/Users/Baggio/C++/Lab0/Lab0/Lab0/main.cpp"
Files: /Users/Baggio/C++/Lab0/Lab0/Lab0/result.txt, /Users/Baggio/C++/Lab0/Lab0/Lab0/dict.txt
Here's the code snippet:
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <cstdlib>
using namespace std;
int main(int argc, const char * argv[]) {
// string dict_filename = "/Users/Baggio/C++/Lab0/Lab0/Lab0/dict.txt";
// string result_filename = "/Users/Baggio/C++/Lab0/Lab0/Lab0/result.txt";
string dict_filename_string = "dict.txt";
string result_filename_string = "result.txt";
const char* dict_filename = dict_filename_string.c_str();
const char* result_filename = result_filename_string.c_str();
// open files
ifstream dict_file(dict_filename, ifstream::in);
ifstream result_file(result_filename, ifstream::in);
if (!dict_file || !result_file) {
cerr << "File could not be opened." << endl;
exit(1);
}
}
Result of execution
File could not be opened.
I'm sure I've done all the includes right, and the data types right for the ifstream constructor arguments. The only thing I can think of worth mentioning is the system I'm on: I'm on a Mac and I'm using XCode6 as my IDE.
Also, I've tried to move the files' location (results.txt and dict.txt) to these locations to no avail:
/Users/Baggio/C++/Lab0/Lab0/Lab0/
/Users/Baggio/C++/Lab0/Lab0/
/Users/Baggio/C++/Lab0/
/Users/Baggio/C++/
Thanks for your help guys!! Any suggestions or thoughts appreciated.
Print out your current working directory when you run the program:
char buffer[256];
char *val = getcwd(buffer, sizeof(buffer));
if (val) {
std::cout << buffer << std::endl;
}
This will tell you where you are running your program from and thus why the path doesn't match for relative paths. A relative path is relative to the current working directory, not to where your binary is located.
If you want to make the path relative to the location of the binary then you will have to do that yourself. Many programming languages offer this as an option, but it is not built-in to C++. You can do this by finding the executable using the argv[0] from main. Then you need to drop the file component of the executable path and replace it with the file name that you are interested in.
Since C++17, you can use std::filesystem::current_path() instead of getcwd.
std::cout << std::filesystem::current_path() << std::endl;

How to use argv as variable

I would like to use a given Variable for my Main-function in c++ to be used as part of the name of the outputfile. The code is:
int main(int argc, char* argv[]) {
fstream f,g;
string s1,s2,name;
name = argv[5];
s1 = name+("_systemvalues.dat");
f.open(s1.c_str(), ios::out);
...
c.close();
An, for example, argv[5] should be "test". The program is compiling and it is running as well, but the output file is not produced.
I can display s1 on the terminal and it is what it should be - but the outputfile is simply not produced.
Maybe you don't have the required write permissions to make changes to the filesystem / directory.
chmod -R 777 mydir
By the way you could use std::ofstream for the job. It will create the file for you if it doesn't already exist.
#include <string>
#include <iostream>
#include <fstream>
/* ... */
std::string name = "";
name.append(argv[5]);
name.append("_systemvalues.dat");
std::ofstream out(name.c_str());
out << "text" << std::endl;
out.close();
/* ... */
Ok... the problem was the input. The character '/' cannnot be used as part of a filename - with hindsight it is really clear.