Search code examples
c++linuxc++11opendir

Why is opendir() randomly resulting in ENOENT?


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.


Solution

  • 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.