I'm using the non-standard function warn() (provided by BSD) to output an error message if a file can't be opened, like so:
std::string path = get_path() ;
std::ifstream file(path) ;
if (file.is_open()) { /* do something */ }
else {
warn("%s", path.c_str()) ;
// uses errno to figure out what the error was and outputs it nicely along with the filename
}
That's all very well for outputting it, but what if I want to use the entire string somewhere else, in addition to printing it? The warn() functions don't seem to have a form that writes the error to a string. I've tried rolling my own, but it seems awfully cumbersome in comparison (besides not getting the program's name):
this->foo((boost::format("%s: %s") % path % strerror(errno)).str()) ;
So how do I get warn()'s output as a string?
warn puts its output on the standard error output. So you would have to create a mechanism to redirect standard error output to a location that you can read back into a string. The most straight forward way may be to redirect standard error to a file, and then read the file back as a string. You could, for instance, try to use dup2() to accomplish this (as explained in the answer to this question).
However, wrapping your own version of warn is probably a better choice. You may consider the C vsnprintf() function to implement it, though. There are answers to this question that address both using boost::format and vsnprintf().
You're right — there's no sprintf analog (i.e. that is, no hypothetical swarn function).
Your approach seems viable.
It would appear that your gyrations produce a result similar to:
path + ": " + strerror(errno);
At a guess, the "program's name" that it's including is probably just argv[0], so you could apparently produce a roughly equivalent of your warn that just returns a std::string with something on this general order:
std::string warn_s(std::string const &path) {
char *pname = strrchr(argv[0], '/');
if (pname == NULL)
pname = argv[0];
return path + pname + ": " + strerror(errno);
}
The major difficulty here is that argv is local to main, so you'll probably need to either save it into an accessible location in main, or else use some non-standard mechanism to re-retrieve that data in your function.
Unfortunately, the documentation for warn I was able to find was poor enough that a bit of testing/trial and error will probably be needed if you want to duplicate its output precisely.
Related
I have a url like that:
http://localhost:3000/get_agencies?zipcodecity=&zipcode=30048&city=kraków&
As you can see there city param is equal to kraków. When I pass such URL into curl I receive it somehow encoded in inappropriate way:
curl = curl_easy_init();
// Some code here
curl_easy_setopt(curl, CURLOPT_URL, url);
On the server side I get city=kraków. I tried to use curl_easy_escape(curl, url, strlen(url)); but it just encodes everything. So how can I parse only param values of a query string?
(sorry, either you significantly edited your original question, or i read it wrong the first time, let me try again)
well, i guess you can kindof repair it, guessing where the data name and value starts and ends based on the = and & characters. it's NOT foolproof, if & or ? is wrongly encoded, or if you encounter an unicode character using the equivalent bytes for their character (edit: this last part is fixable by switching to a unicode string search function), this won't be enough, but except for those 2 scenarios, something like this should work:
std::string patchInappropriatelyEncodedURL(CURL *curl, std::string url){
size_t pos=url.find("?");
size_t pos2;
if(pos==url.npos){
return url;
}
std::string ret=url.substr(0,pos+1);
std::string tmpstr;
char *escapedstr;
url=url.substr(pos+1,url.npos);
std::string type="=";
do{
pos=url.find("=");
pos2=url.find("&");
if(pos == url.npos && pos2 == url.npos){
break;
}
if(pos<pos2){
type="=";
}else{
type="&";
pos=pos2;
}
tmpstr=url.substr(0,pos);
url=url.substr(pos+1,url.npos);
escapedstr=curl_easy_escape(curl,tmpstr.c_str(),tmpstr.length());
ret.append(escapedstr);
ret.append(type);
curl_free(escapedstr);
}while(true);
escapedstr=curl_easy_escape(curl,url.c_str(),url.length());
ret.append(escapedstr);
curl_free(escapedstr);
return ret;
}
note that this function is based on guessing, and is not by any means foolproof. i suppose the guessing could improved with a dictionary for your target language or something, though.. but your time would probably be better spent on fixing the bug causing you to receive malformed urls in your program in the first place.
i deliberately omitted error checking because i'm lazy. curl_easy_escape can fail (out of memory), and when it does, it returns a nullptr. you should fix that before the code enters production, i'm too lazy.
you should put those curl_free's in a finally{} block, else you may encounter memory leaks if the string functions throw exceptions (like substr may throw bad_alloc exceptions), but again, i'm too lazy to fix it.
this is why we have curl_easy_escape.
char *escaped_string=curl_easy_escape(ch,"kraków",0);
(however, when the string is known at compile time, you could hardcode the encoded version instead of encoding it at runtime, in this case, the hardcoded version is krak%C3%B3w - your browser's javascript console can be used to figure that out, just write encodeURIComponent("kraków"); to see what the urlencoded version looks like)
gotchas:
when the 3rd paramater is 0, curl use strlen() to determine the size. this is safe when using utf8 text, but not safe with binary data. if you're encoding binary data, make sure to specify the length manually, as strlen() will stop once it finds a null byte. (other than that, curl_easy_escape, and urlencoded data is binary safe)
don't forget to curl_free(escaped_string); when you're done with it, else you'll end up with memory leaks.
I want to achieve the equivalent of "./myfile < input.txt" using execl():
execl("path/myfile", ",myfile", "< input.txt");
execl("/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "buf < input", NULL);
execlp("/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf", "input");
but the command fails...
I want 'input.txt' and '<' command through myfile using execl — how do I do it?
Your code has to do the I/O redirection before you run the execl() code.
If you want to achieve the effect of a shell running:
/user/Desktop/Fuzzer/clear/easy_fuzzer/buf < input
then you will need to write something like this in the child code:
const char *filename = "input"; // or "input.txt" — the question uses both
int fd = open(file, O_RDONLY);
if (fd < 0)
err_syserr("failed to open file %s for reading\n", filename);
if (dup2(fd, STDIN_FILENO) < 0)
err_syserr("failed to redirect %s to standard input\n", filename);
close(fd); // In theory, it could fail, but there isn't much you can do about it
const char *cmdpath = "/home/user/Desktop/Fuzzer/clear/easy_fuzzer/buf";
execl(cmdpath, "buf", (char *)NULL);
err_syserr("failed to execute program %s\n", cmdpath);
This should normally all be in the code executed by the child.
You can find the code for the err_syserr() function in stderr.c and stderr.h from https://github.com/jleffler/soq/tree/master/src/libsoq. One line error handling makes it less onerous than writing out multiple lines. Note that there's no reason to check the return value from any of the exec*() functions. If the function returns, it failed. If it succeeds, there's a different process running in place of the current process.
If you like doing things the long-winded way, you can investigate whether your system supports posix_spawn() and its colleagues. You can do all sorts of things by setting up appropriate sequences of attributes. For my money, it is far simpler and clearer to write the code as shown above.
I would probably not use execl() — I'd probably use execv() (or perhaps execvp()) because it allows the argument list to be fixed at run-time instead of mandating that it is fixed at compile time. The code passes buf as the value for argv[0] to the executed program. If you want the full path name as argv[0] you can do that.
Note that if the file name part of the first argument to execlp() (or execvp(), or any other path-searching exec*() function) contains any / at all, then there is no path-based search performed, so it is not appropriate to use them if the command name is an absolute path name as in the example.
What is wrong with this piece of code? Is there another way to do this?
It keeps throwing std::out_of_range error.
std::wstring ext(FileInformation.cFileName);
ext = ext.substr(ext.find(L"."));
What's wrong is that you are not handling the case that the file has no extension.
What happens is that ext.find(L".") returns std::wstring::npos (the highest possible number - indicating "not found") because it doesn't find a dot.
You are then calling ext.substr(std::wstring::npos) which is of course out of range.
You have to check for this case:
std::wstring ext(FileInformation.cFileName);
std::size_t dotPos = ext.find(L".");
if(dotPos != std::wstring::npos) {
ext = ext.substr(dotPos);
} else {
ext = L"."; // assuming you want to treat an empty extension like this
}
However, if your goal is to extract the file extension, there are some more gotchas you need to be aware of:
Windows considers only the part after the last dot as the file extension. Your code will give .a.b for a file named file.a.b while Windows will consider the file extension to be just .b. So you probably need to use rfind instead of find, which searches backwards.
But then there is another subtlety: A file extension can't contain a space (file.hello world is a file without extension), so you would need to check this as well...
Therefore, since you are obviously already using WinAPI, I'd advise you to use the WinAPI function made for exactly this purpose: PathFindExtension. This way, you can't get it wrong.
Example (assuming you still want a dot - remove it otherwise):
ext = std::wstring(L".") + *PathFindExtension(ext.c_str());
Alternatively, there would also be the boost library which also has a way to extract the file extension using boost::path::extension, but it's kinda heavy and if you don't already use boost, it's not worth considering it.
FileInformation.cFileName did not include a dot then find() will return string::npos.
So you need to check for string::npos at first befor using substr cause if first argument to substr is greater than the string length, it throws out_of_range.
There can be no "." in FileInformation.cFileName and find will return std::wstring::npos so first check the return of find and then call substr.
Maybe something like this:
std::wstring ext(FileInformation.cFileName);
std::size_t found=ext.find(L".");
if (found!=std::wstring::npos)
ext = ext.substr(found);
Just like the title says, I've been working on a fairly large program and have come upon this bug. I'm also open to alternatives for searching a file for a string instead of using . Here is my code narrowed down:
istreambuf_iterator<char> eof;
ifstream fin;
fin.clear();
fin.open(filename.c_str());
if(fin.good()){
//I outputted text to a file to make sure opening the file worked, which it does
}
//term was not found.
if(eof == search(istreambuf_iterator<char>(fin), eof, term.begin(), term.end()){
//PROBLEM: this code always executes even when the string term is in the file.
}
So just to clarify, my program worked correctly in Linux but now that I have it in a win32 app project in vs2010, the application builds just fine but the search function isn't working like it normally did. (What I mean by normal is that the code in the if statement didn't execute because, where as now it always executes.)
NOTE: The file is a .xml file and the string term is simply "administration."
One thing that might or might not be important is to know that filename (filename from the code above) is a XML file I have created in the program myself using the code below. Pretty much I create an identical xml file form the pre-existing one except for it is all lower case and in a new location.
void toLowerFile(string filename, string newloc, string& newfilename){
//variables
ifstream fin;
ofstream fout;
string temp = "/";
newfilename = newloc + temp + newfilename;
//open file to read
fin.open(filename.c_str());
//open file to write
fout.open(newfilename.c_str());
//loop through and read line, lower case, and write
while (fin.good()){
getline (fin,temp);
//write lower case version
toLowerString(temp);
fout << temp << endl;
}
//close files
fout.close();
fin.close();
}
void toLowerString(string& data){
std::transform(data.begin(), data.end(), data.begin(), ::tolower);
}
I'm afraid your code is invalid - the search algorithm requires forward iterators, but istreambuf_iterator is only an input iterator.
Conceptually that makes sense - the algorithm needs to backtrack on a partial match, but the stream may not support backtracking.
The actual behaviour is undefined - so the implementation is allowed to be helpful and make it seem to work, but doesn't have to.
I think you either need to copy the input, or use a smarter search algorithm (single-pass is possible) or a smarter iterator.
(In an ideal world at least one of the compilers would have warned you about this.)
Generally, with Microsoft's compiler, if your program compiles and links a main() function rather than a wmain() function, everything defaults to char. It would be wchar_t or WCHAR if you have a wmain(). If you have tmain() instead, then you are at the mercy of your compiler/make settings and it's the UNICODE macro that determines which flavor your program uses. But I doubt that char_t/wchar_t mismatch is actually the issue here because I think you would have got an warning or error if all four of the search parameters didn't use the same the same character width.
This is a bit of a guess, but try this:
if(eof == search(istreambuf_iterator<char>(fin.rdbuf()), eof, term.begin(), term.end())
Alright here's the deal, I'm taking an intro to C++ class at my university and am having trouble figuring out how to change the extension of a file. First, what we are suppose to do is read in a .txt file and count words, sentences, vowels etc. Well I got this but the next step is what's troubling me. We are then suppose to create a new file using the same file name as the input file but with the extension .code instead of .txt (in that new file we are then to encode the string by adding random numbers to the ASCII code of each character if you were interested). Being a beginner in programming, I'm not quite sure how to do this. I'm using the following piece of code to at first get the input file:
cout << "Enter filename: ";
cin >> filename;
infile.open(filename.c_str());
I'm assuming to create a new file I'm going to be using something like:
outfile.open("test.code");
But I won't know what the file name is until the user enters it so I can't say "test.txt". So if anyone knows how to change that extenstion when I create a new file I would very much appreciate it!
I occasionally ask myself this question and end up on this page, so for future reference, here is the single-line syntax:
string newfilename=filename.substr(0,filename.find_last_of('.'))+".code";
There are several approaches to this.
You can take the super lazy approach, and have them enter in just the file name, and not the .txt extension. In which case you can append .txt to it to open the input file.
infile.open(filename + ".txt");
Then you just call
outfile.open(filename + ".code");
The next approach would be to take the entire filename including extension, and just append .code to it so you'd have test.txt.code.
It's a bit ambiguous if this is acceptable or not.
Finally, you can use std::string methods find, and replace to get the filename with no extension, and use that.
Of course, if this were not homework but a real-world project, you'd probably do yourself -- as well as other people reading your code -- a favor by using Boost.Filesystem's replace_extension() instead of rolling your own. There's just no functionality that is simple enough that you couldn't come up with a bug, at least in some corner case.
Not to give it away since learning is the whole point of the exercise, but here's a hint.
You're probably going to want a combination of find_last_of and replace.
Here is a few hints. You have a filename already entered - what you want to do is get the part of the filename that doesn't include the extension:
std::string basename(const std::string &filename)
{
// fill this bit in
}
Having written that function, you can use it to create the name of the new file:
std::string codeFile = basename(filename) + ".code";
outFile.open(codeFile);
Pseudo code would be to do something like
outFilename = filename;
<change outFilename>
outfile.open(outFilename);
For changing outFilename, look at strrchr and strcpy as a starting point (might be more appropriate methods -- that would work great with a char* though)
In Windows (at least) you can use _splitpath to dissect the base name from the rest of the pieces, and then reassemble them using your favorite string formatter.
why not using the string method find_last_of() ?
std::string new_filename = filename;
size_type result = new_filename.find_last_of('.');
// Does new_filename.erase(std::string::npos) working here in place of this following test?
if (std::string::npos != result)
new_filename.erase(result);
// append extension:
filename.append(".code");
I would just append ".code" to the filename the user entered. If they entered "test.txt" then the output file would be "test.txt.code". If they entered a file name with no extension, like "test" then the output file would be "test.code".
I use this technique all the time with programs that generate output files and some sort of related logging/diagnostic output. It's simple to implement and, in my opinion, makes the relationships between files much more explicit.
How about using strstr:
char* lastSlash;
char* newExtension = ".code";
ChangeFileExtension(char* filename) {
lastSlash = strstr(filename, ".");
strcpy(lastSlash, newExtension);
}
What you'll need to do is copy the original filename into a new variable where you can change the extension. Something like this:
string outFilename;
size_t extPos = filename.rfind('.');
if (extPos != string::npos)
{
// Copy everything up to (but not including) the '.'
outFilename.assign(filename, 0, extPos);
// Add the new extension.
outFilename.append(".code");
// outFilename now has the filename with the .code extension.
}
It's possible you could use the "filename" variable if you don't need to keep the original filename around for later use. In that case you could just use:
size_t extPos = filename.rfind('.');
if (extPos != string::npos)
{
// Erase the current extension.
filename.erase(extPos);
// Add the new extension.
filename.append(".code");
}
The key is to look at the definition of the C++ string class and understand what each member function does. Using rfind will search backwards through the string and you won't accidentally hit any extensions in folder names that might be part of the original filename (e.g. "C:\MyStuff.School\MyFile.txt"). When working with the offsets from find, rfind, etc., you'll also want to be careful to use them properly when passing them as counts to other methods (e.g. do you use assign(filename, 0, extPos-1), assign(filename, 0, extPos), assign(filename, 0, extPos+1)).
Hope that helps.
size_t pos = filename.rfind('.');
if(pos != string::npos)
filename.replace(pos, filename.length() - pos, ".code");
else
filename.append(".code");
Very Easy:
string str = "file.ext";
str[str.size()-3]='a';
str[str.size()-2]='b';
str[str.size()-1]='c';
cout<<str;
Result:
"file.abc"