Using dirent->d_name together with string fails - c++

I'm writing an C++ Application that uses the dirent.h library, to read files from a directory. At one point I want to decide between Files and directories. To achieve that I added the following piece of code:
entry = readdir(used_directory); //read next object from directory stream
DIR* directory_test = opendir((path + entry->d_name).c_str()); //try to open object as directory
if ( directory_test != nullptr) { //object is directory
if (entry != nullptr) { //reading from directory succeeded
dirs.push_back(entry->d_name); //add filename to file list
++dircounter;
}
}
else { //object is file
path is type of string and entry is type of dirent *.
With this, the program causes an memory access error, without it doesn't.
I figured out, that the error is caused by the
(path + entry->d_name)
But it is not the implicit conversion to string in the statement, because other tests like cout << entry->d_name; or path += entry->d_name failed with the same error, too. So obviously there is a failure with using entry->d_name as char *, although it is defined so (in the documentation of dirent.h).
Why is this failure occuring?
EDIT:
Later in the program I add entry->d_name to a vector<string>, that doesn't cause any problems.

The failure was accessing entry before checking if it's equal to nullptr.
Because my loop itterating through the directory is stopped if entry is equal to nullptr, the last itteration causes the error.

Related

ifstream not working with dirent.h

I'm testing optimizations for dijkstra algorithm and to make it easier to open files I used "dirent.h" to get all the test files in the running path and then ifstream to open this file.
the readDirec method reads all the files in the directory and ignores folder and puts those files names in a vector called files.
void selectDirec(){
files.clear();
DIR *dir;
struct dirent *ent;
if ((dir = opendir (".")) != NULL) {
while ((ent = readdir (dir)) != NULL) {
if(opendir(ent->d_name) == NULL){
files.push_back(ent->d_name);
}
}
closedir (dir);
} else {
cout<<"directory error"<<endl;
}
}
after that I uses a function called selectFile which assigns the name of the file the user chooses to a variable called fileName.
void selectFile(){
selectDirec();
for(int i = 0 ; i < files.size() ; i++){
cout<<i+1<<" : "<<files[i]<<endl;
}
int choice = 0;
do{
cout<<"enter file number"<<endl;
cin>>choice;
}while(choice > files.size());
choice--;
fileName = files[choice];
cout<<fileName<<":"<<endl;
}
after that I enter my readGraph function which opens the file and continue graph operations
void readGraph(){
ifstream ifile; ifile.open(fileName);
if(!ifile.is_open()){
cout<<"no file with the name specified"<<endl;
eflag = true;
return;
}
...
...
}
initialization:
vector<char *> files;
char * fileName ;
now I have those 5 files to test which I got from here http://algs4.cs.princeton.edu/44sp/:
tinyEWD.txt contains 8 vertices and 15 edges [140B]
mediumEWD.txt contains 250 vertices and 2,546 edges[40KB]
1000EWG.txt contains 1,000 vertices and 16,866 edges[313KB]
10000EWG.txt contains 10,000 vertices and 123,462 edges[2.4MB]
NYC.txt . contains 264346 vertices and 733846 edges[12.7MB].
but there's a weird problem with those 3 files:
'mediumEWD' , '10000EWD.txt' , 'NYC.txt'
when I choose any of them the code shows me "no file with the name specified" that in the else statement in readGraph.
but when I enter their name manually and comment selectDirec and selectFile the program opens them successfully.
P.S. I checked the file name and spacing and everything.
P.S.2 currently running this code on ubuntu 14.04 LTS.
thanks in advance.
if(opendir(ent->d_name) == NULL){
files.push_back(ent->d_name);
}
What is files? I suspect that you are using a std::vector<const char *>, or something along the same lines.
This won't work. d_name is a part of the dirent structure. Immediately afterwards, and certainly after the closedir(), that pointer is no longer valid, and points to deallocated memory.
Looks to me like you then proceed and attempt to use the no-longer valid pointer as the filename parameter to std::ifstream.
You should use a std::vector<std::string> to store the filenames, and use the c_str() member function to extract a pointer to a C-style string, for the open() call.
You can't be using a vector of std::strings here, this must be a vector of raw character pointers. That's because you're assigning one of its values to fileName, whatever it is, and then passing it directly to open() without using c_str(). So it can't be a vector of strings.

RapidXML: "expected <" error at end-of-file related to whitespace bug?

I created a C++ application that reads in XML files with the RapidXML parser. At one XML file that was shaped exactly the same as another one that worked, the parser threw an error:
"expected <"
The last five characters before the error were from the closing tag of the root element, so the error happened at the end-of-file:
</UW>
What I suspect this error to be related to, is a whitespace skipping bug being an issue with RapidXML v1.12 (I am using v1.13). I used no parsing flags (doc.parse<0>(bfr);).
According to this site, the bug was believed to be caused by faulty implementation of the "parse_trim_whitespace" parse flag. A patch was provided on that site, but there also seemed to be a problem with that patch.
The following is the XML document that caused this error. What I also don't understand - besides the reason for the error - is why the error didn't happen parsing another file with content of the same fashion. My application also successfully parses several other files before that file.
<?xml version="1.0" encoding="UTF-8"?>
<UW>
<Bez>EV005</Bez>
<Herst>Trumpf</Herst>
<Gesw>16</Gesw>
<Rad>1.6</Rad>
<Hoehe>100</Hoehe>
<Wkl>30</Wkl>
<BgVerf>Freibiegen</BgVerf>
<MaxBel>50</MaxBel>
<Kontur>0</Kontur>
<Grafik>0</Grafik>
</UW>
Part of my application were the error occours (this is the inside of a loop):
// Get "Bezeichnung" attribute
attr = subnode->first_attribute("Bezeichnung");
if ( !attr ){ err(ERR_FILE_INVALID,"Werkzeuge.xml"); return 0; }
name = attr->value();
// Get file name/URL
string fileName = name;
fileName.append(".xml");
// Open file
ifstream werkzeugFile(concatURL(PFAD_WERKZEUGE,fileName));
if(!werkzeugFile.is_open()) { err(ERR_FILE_NOTFOUND,fileName); return 0; }
// Get length
werkzeugFile.seekg(0,werkzeugFile.end);
int len = werkzeugFile.tellg();
werkzeugFile.seekg(0,werkzeugFile.beg);
// Allocate buffer
char * bfr = new char [len+1];
werkzeugFile.read(bfr,len);
werkzeugFile.close();
// Parse
SetWindowText(hwndProgress,"Parsing data: Werkzeuge/*.xml");
btmDoc.parse<0>(bfr);
// Get type of tool & check validity
xml_node<> *rt_node = btmDoc.first_node();
if ( strcmp(rt_node->name(),"OW") == 0 ){
isOW = true;
}
else if ( strcmp(rt_node->name(),"UW") == 0 ){
isUW = true;
}
else { err(ERR_FILE_INVALID,fileName); return 0; }
// Prepare for next loop iteration
delete[] bfr;
btmDoc.clear();
subnode = subnode->next_sibling();
Ah, I think I see it. Two things:
First, the ifstream is suspicious -- shouldn't it be opened in binary mode if you're jumping around in it using byte offsets (and somebody else is doing the parsing)? Passstd::ios::in | std::ios::binary as the second argument to the ifstream constructor.
Second, your memory management seems fine, except that you allocate one byte extra (the +1) but never seem to make use of it. I'm assuming you're missing bfr[len] = '\0'; after the contents are read in -- this explains the odd parse error at the end of the file, since the XML parser doesn't know it reached the end of the file -- it's parsing a null terminated string that isn't null terminated, and tries to parse random bytes of memory ;-)

readdir(): re-reading certain files

I got a function which task is to rename all files in a folder however, it re-rename certain files:
http://i.imgur.com/JjN8Qb2.png, the same kind of "error" keeps occurring for every tenth number onwards. What exactly is causing this "error"?
The two arguments to the function is the path for the folder and what start value the first file should have.
int lookup(std::string path, int *start){
int number_of_chars;
std::string old_s, file_format, new_s;
std::stringstream out;
DIR *dir;
struct dirent *ent;
dir = opendir (path.c_str());
if (dir != NULL) {
// Read pass "." and ".."
ent = readdir(dir);
ent = readdir(dir);
// Change name of all the files in the folder
while((ent = readdir (dir)) != NULL){
// Old string value
old_s = path;
old_s.append(ent->d_name);
// Get the format of the image
file_format = ent->d_name;
number_of_chars = file_format.rfind(".");
file_format.erase(0,number_of_chars);
// New string value
new_s = path;
out << *start;
new_s += out.str();
new_s.append(file_format);
std::cout << "Successfully changed name on " << ent->d_name << "\tto:\t" << *start << file_format << std::endl;
// Switch name on the file from old string to new string
rename(old_s.c_str(), new_s.c_str());
out.str("");
*start = *start+1;
}
closedir (dir);
}
// Couldn't open
else{
std::cerr << "\nCouldn't open folder, check admin privileges and/or provided file path\n" << std::endl;
return 1;
}
return 0;
}
You are renaming files to the same folder in which the original files were, resulting in an infinite loop. You renamed 04.png to 4.png but since you are iterating over all files in the folder, at some point you're going to iterate to the "new" 4.png file (in your smaple, on the 40th iteration) and rename that file to 40.png and so on...
The easiest way to resolve this with minimal changes to the existing code is to "rename" (move) the files to a temporary folder with their new names. Something like:
new_s = temp_path;
out << *start;
new_s += out.str();
new_s.append(file_format);
// Switch name on the file from old string to new string
rename(old_s.c_str(), new_s.c_str());
and when you are done renaming all the files in path (outside the while loop), delete the folder and "rename" (move) temp_path to `path:
closedir (dir);
deletedir(path);
rename(temp_path, path);
`
Possible problems I see:
Renaming files causes them to be fed to your algorithm twice.
Your algorithm for computing the new filename is wrong.
You should be able to write a test for this easily, which in turn should help you fix the problem or write a more specific question. Other than that, I don't see any grave issues, but it would help if you reduced the scope of variables a bit, which would make sure that different iterations don't influence each other.

Using the filename generated from mkstemp

The mkstemp() function generates a unique temporary filename from template, creates and opens the file, and returns an open file descriptor for the file. The last six characters of template must be "XXXXXX" and these are replaced with a string that makes the filename unique. Since it will be modified, template must not be a string constant, but should be declared as a character array.
After template is replaced with a string that makes the filename unique, I save the string to later use. This is where I'm encountering a strange problem I can't seem to wrap my head around. I can print the correct filename to my terminal, see the file in my file explorer and open it to see the correct contents, but when I include the string as part of a command to execute with popen() I get a pointer to an empty file. When I hard code the names of the tempory files back into my code however and run again, I get the correct result I am expecting. Is there something I'm overlooking or missing? Here is a code snippet:
char tmpname[] = "tmp.XXXXXX";
FILE *fpt = fdopen(mkstemp(tmpname), "w");
string saved_tmpname(tmpname);
// blah
// write to file
// blah blah
const string command = "mycommand " + saved_tmpname;
cout << command << endl; // prints correctly
FILE *fpipe = popen(command.c_str(), "r");
if (fpipe == NULL) {
perror(command.c_str());
}
char buff[4096];
while (fgets(buff, 4096, fpipe)) {
// we don't get here!
}
From the manpage for mkstemp:
The file is opened with the open(2) O_EXCL flag, guaranteeing that the caller is the process that creates the file.
The O_EXCL flag prevents you from opening the file again. This is OK, since it is a temporary file - only one process (the creator) should have access to it. Temporary files contain sensitive data sometimes.

localtime alternative that won't overwrite the supplied struct

Essentially, what I'm trying to do is to check the last access time of a file and compare it with a string. Here's the relevant block:
struct stat file;
char timeStr[ 100 ];
stat(nodes.at(0), &file);
strftime(timeStr, 100, "%H:%M:%S-%m/%d/%y", localtime(&file.st_atime)); /* problem */
nodes is a vector of file paths; I'm not sure if it's relevant but I'll include the code that I'm using to set nodes:
vector<char*> nodes;
DIR *dir;
struct dirent *cur
if((dir = opendir(searchPath.c_str())) == NULL) {
cout << "Error opening search path. Are you sure '"
<< searchPath.c_str() << "' is a valid search path?" << endl;
return 0;
}
while((cur = readdir(dir)) != NULL) {
if(string(cur->d_name) == "." || string(cur->d_name) == "..") continue;
nodes.push_back(cur->d_name);
}
closedir(dir);
Where searchPath is a user-inputted string.
The problem: when the 'problem' line is run, from there on nodes is a vector of garbage. I'm wondering if I can accomplish this task without turning nodes into garbage.
Since this is homework, and as you can probably tell I'm not used to C++, a solid push in the right direction will be given the 'accept'.
Thank you.
It has nothing to do with your strftime call but with the fact that (from here):
The pointer returned by readdir() points to data which may be overwritten by another call to readdir() on the same directory stream.
Since you're simply pushing a character pointer that points to data that may be overwritten by subsequent calls to readdir, you may well end up with garbage.
You can probably fix it by using a copy of the C string with something like:
nodes.push_back (strdup (cur->d_name)); // plus error handling if need be.
And, if your implementation doesn't have a strdup (it's not part of the standard), you can use mine (found here).
nodes.push_back(cur->d_name);
You're storing pointers in the vector that immediately become invalid (cur is valid until the next readdir or closedir call). The best fix is to code what you want -- make nodes a vector of strings. The easiest fix:
nodes.push_back(strdup(cur->d_name));