Search code examples
c++boostdirectorypasswordsgenerator

Using boost::filesystem, how can I prevent overwriting of previous password files?


Alright, I'm fairly new to C++ and coding in general. I'm working on a password generator, that randomly generates a number between 33 and 126, then uses the corresponding ASCII code and creates a password of user-specified length. And then I want to use boost::filesystem to create a passwords folder, and in that folder, use std::ofstream to create a text file with the password in it. I've figured out how to create the folder, and how to create the file. But... I need a way to determine if there are already password files in the folder, and if so, create differently named password files, in numerical order(because writing to the file overwrites previous passwords). For example, if I were to run the program three times, inside the passwords folder would be three files, named 'password.txt', 'password2.txt' and 'password3.txt'. Here is what I have so far:

namespace fs = boost::filesystem;

void savePassword(char array[], fs::path dir)
{
    std::cout << "Saving...\n";
    //this is where there needs to be a check, and the check needs to return how many password files there are
    std::ofstream savePw;
    savePw.open("./Passwords/password.txt"); //check to see if file exists before this, if so, make it "./Passwords/password" += number of files += ".txt"
    if (!savePw) {
        std::cout << "ERROR: File could not be opened.\n";
    }
    else {
        savePw << "Your password is:\n" << static_cast<std::string>(array) << '\n';
        savePw.close();
        std::cout << "Saved to: " << dir << '\n' << "Thanks for using the SPG!\n";
    }
}

fs::path createFolder() //creates folder and returns the directory to be passed to savePassword()
{
    fs::path dir{ boost::dll::program_location().parent_path() += "\\Passwords" };
    fs::create_directory(dir);
    return dir;
}

Done quite a bit of research but haven't really turned anything up. I've tried boost::filesystem::directory_iterator, but don't really understand how it's to be used. I've also tried converting the path to string and using std::stoi, but that was a failure. Any help is much appreciated. :)


Solution

  • Boost.Filesystem offers an operation called exists, along with other operations for querying status of a file. You could generate the file name using a loop like below:

    fs::path generate_filename(fs::path const& dir)
    {
        fs::path path = dir / "password.txt";
        unsigned int counter = 1u;
        while (true)
        {
            if (!fs::exists(path))
                break;
    
            ++counter;
            if (counter == 0u)
                throw std::runtime_error("Failed to generate a unique file name");
    
            path = dir / std::string("password")
                .append(std::to_string(counter)).append(".txt");
        }
    
        return path;
    }
    

    However, note that the code is racy, because the file can be created between the call to exists and opening the file for writing. The solution is to test if the file exists while opening the file.

    As of C++20 standard C++ file streams do not allow to test if the file exists while opening the file for writing, so you will have to use a different file API. Starting with C11, fopen allows to specify "x" in the file open mode to indicate that the function must fail if the file exists. You have to consult with your compiler or standard C library documentation to see if it supports this new addition.

    Alternatively, you could use low level filesystem API, such as open call with O_CREAT and O_EXCL flags on POSIX systems or CreateFile with CREATE_NEW flag on Windows.


    A couple of unrelated notes.

    1. You don't need to static_cast<std::string>(array) to output a C-style string to a stream. Directly outputting a character array will work - the characters from the array will be written to the stream up until the first null character (terminator). By casting you just unnecessarily allocate dynamic memory and copy the string before outputting.

    2. Avoid accepting potentially large objects, like containers, strings or paths, in function arguments by value, unless required by the function body. Your fs::path dir argument could be better accepted as a const reference (fs::path const& dir), which would potentially avoid having to copy the path at the point of calling savePassword.