To retrieve the contents of a directory (recursively) on Linux Ubuntu, I'm using the following RAII struct:
struct L_DirectoryReader
{
//Fields
L_PathData pathData;
DIR *dirHandle;
struct dirent *currentDirEntry;
//Method declaration happens before constructor,
//because it is used in the constructor.
void readDir()
{
errno = 0;
this->currentDirEntry = readdir(this->dirHandle);
//Error checking
if(errno != 0)
{
int errorcode = errno;
switch(errorcode)
{
default:
{
throw os3util::fileh::exc::FileIOException(
std::string("Failed to retrieve contents of dir:")
+ kNEWLINE_STR + this->pathData.relativePath.getFullPath()
+ kNEWLINE_STR + "with readdir() errorcode "
+ os3util::strfunc::intToString(errorcode)
+ std::string(".")
);
}
}
}
}
//Constructor
L_DirectoryReader(const L_PathData& pathData) :
pathData(pathData),
dirHandle(),
currentDirEntry()
{
//Obtain file handle
const char* openDirPathCString = (kSLASH_STR + this->pathData.absolutePath.getFullPath()).c_str();
errno = 0;
this->dirHandle = opendir(openDirPathCString);
//Check file handle validity
if(this->dirHandle == nullptr)
{
int errorCode = errno;
switch(errorCode)
{
case EACCES:
{
throw os3util::fileh::exc::FileIOException(std::string("Could not find directory:") + kNEWLINE_STR + this->pathData.relativePath.getFullPath() + kNEWLINE_STR + std::string("permission denied."));
} break;
case ENOENT:
{
throw os3util::fileh::exc::FileIOException(std::string("Could not find directory:") + kNEWLINE_STR + this->pathData.relativePath.getFullPath() + kNEWLINE_STR + std::string("does not exist."));
} break;
case ENOTDIR:
{
throw os3util::fileh::exc::FileIOException(std::string("Could not find directory:") + kNEWLINE_STR + this->pathData.relativePath.getFullPath() + kNEWLINE_STR + std::string("is not a directory."));
} break;
default:
{
throw os3util::fileh::exc::FileIOException(std::string("Could not find directory:") + kNEWLINE_STR + this->pathData.relativePath.getFullPath() + kNEWLINE_STR + std::string("error code ") + os3util::strfunc::intToString(errorCode) + std::string(" received."));
}
}
}
else
{
try
{
this->readDir();
}
catch(...)
{
closedir(this->dirHandle);
throw;
}
}
}
//Destructor
~L_DirectoryReader()
{
try
{
errno = 0;
closedir(this->dirHandle);
if(errno != 0)
{
int errorcode = errno;
std::cout << "failed to close dirhandle for directory \"" << this->pathData.relativePath.getFullPath() << "\" with error code " << errorcode << std::endl;
}
}
catch(...)
{
try
{
std::cout << "exception occured while closing dir handle" << std::endl;
}
catch(...)
{
/*Swallow, destructors should never throw*/
}
}
}
};
The problem is, I keep getting the exception for ENOENT
. But not always. I (manually) call it over and over again for the same directory and sometimes it prints all the contents (and the subdirectory contents) as intended and sometimes it throws the exception. Sometimes the first call succeeds, sometimes the first call fails. I don't do anything with the directories at all, they just stay in the same place, untouched.
Other topics with a similar issue that I've found involved not setting errno
to 0, but as the code above shows, I'm doing that. I'm even constructing the const char *
parameter before setting errno
to 0, just in case.
The struct is constructed inside a function. If any exception throws, the destructor activates and closes the DIR
handle. Debugging confirms this. The error messages in the destructor never happen. The only thing that could go wrong there is that dirHandle
is null, and the constructor fails in that scenario.
When I disable the recursion, it always works for the root dir, but it fails frequently for any of the root dir's subdirectories. The root directory is home/myname/os3/serverfiles
. My project is in a completely different directory so I always access it by absolute path.
Every time this struct is accessed it is through the exact same function calls. There is no random factor in my program anywhere.
I'm out of ideas for what could cause this. I'm especially puzzled that the error is ENOENT
when the files clearly exist and it often finds them.
const char* openDirPathCString = (kSLASH_STR + this->pathData.absolutePath.getFullPath()).c_str();
You are calling string::c_str()
on a temporary string that is destroyed at the end of the statement, resulting in a dangling pointer.