3
\$\begingroup\$

The following is a program to shred files securely:

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#define BOOST_FILESYSTEM_NO_DEPRECATED
#include <boost/filesystem.hpp>

using namespace std;
using namespace boost::filesystem;

static ofstream Log;
static stringstream LogErrorStream;  // Buffer soft errors to output them separately after the informational messages in the log file.

// Iterate through a directory and store everything found ( regular files, directories or any other special files ) in the input container
static void DirectoryIterate( const path& dirPath, vector<path>& dirContents )
{
    if ( is_directory( dirPath ) )
    {
        copy( directory_iterator( dirPath ), directory_iterator(), back_inserter( dirContents ) );
    }
}

// Generates a random file name
static string GenerateRandomFileName()
{
    const size_t maxFileNameLength = 19;
    const size_t fileNameLength = ( rand() % maxFileNameLength ) + 1;
    const string acceptedFileNameCharacters = "0123456789abcdefghijklmnopqrstuvwxyz";
    string fileName( fileNameLength, '*' );
    for ( auto& c : fileName )
    {
        c = acceptedFileNameCharacters[rand() % acceptedFileNameCharacters.length()];
    }
    return fileName;
}

// Renames the input file to a random string
static path RandomRename( const path& inputPath )
{
    const size_t maxRenameAttempts = 10; // Max number of attempts to make in renaming a file.
                                         // It is possible that the random filename generator returns a name
                                         // which is same as that of a file in the current directory,
                                         // or is a reserved filename ( like "com1" on windows )
    size_t attempts = 0;
    while ( attempts < maxRenameAttempts )
    {
        const path newPath = inputPath.parent_path() / GenerateRandomFileName();
        if ( !exists( newPath ) )
        {
            boost::system::error_code ec;
            rename( inputPath, newPath, ec );
            if ( ec ) // Maybe a reserved filename was generated
            {
                LogErrorStream << "Failure in renaming " << absolute( inputPath ) << " to " << absolute( newPath ) << " : " << ec.message() << endl;
            }
            else // No error
            {
                return newPath;
            }
        }
        attempts++;
    }
    return inputPath;
}

// Overwrite the input file with random data
// Returns true if successful, false otherwise
static bool WriteRandomData( const path& inputPath )
{
    boost::system::error_code ec;
    const auto inputFileSize = file_size( inputPath, ec );
    if ( ec )
    {
        LogErrorStream << "Failure in determining the size of " << absolute( inputPath ) << " : "  << ec.message() << endl;
        return false;
    }

    ofstream fout( inputPath.string(), ios::binary );
    if ( !fout )
    {
        LogErrorStream << "Failure in opening " << absolute( inputPath ) << " : " << strerror( errno ) << endl;
        return false;
    }

    vector<unsigned char> buffer( inputFileSize );

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with ASCII 255 and ASCII 0 multiple times alternately
    int iterations = 5;
    while ( iterations -- )
    {
        const unsigned char c = ( iterations & 1 ) ? 255 : 0;
        for ( auto& bufferElement : buffer )
        {
            bufferElement = c;
        }
        fout.seekp( 0, ios::beg );
        fout.write( ( char* )&buffer[0], buffer.size() );
        fout.flush();
    }
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with random characters
    for ( auto& bufferElement : buffer )
    {
        bufferElement = rand() % 128;
    }
    fout.seekp( 0, ios::beg );
    fout.write( ( char* )&buffer[0], buffer.size() );
    fout.flush();
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with null characters
    for ( auto& bufferElement : buffer )
    {
        bufferElement = 0;
    }
    fout.seekp( 0, ios::beg );
    fout.write( ( char* )&buffer[0], buffer.size() );
    fout.flush();
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Change file size to 0
    fout.close();
    fout.open( inputPath.string(), ios::binary );
    ////////////////////////////////////////////////////////////////////

    fout.close();
    return true;
}

// Shred a file
static void ShredFile( const path& inputPath )
{
    if ( !WriteRandomData( inputPath ) )
    {
        return;
    }

    const path newPath = RandomRename( inputPath );
    boost::system::error_code ec;
    remove( newPath, ec );
    if ( ec )
    {
        LogErrorStream << "Failure in removing " << newPath << " : " << ec.message() << endl;
    }
}

// Confirm shredding a file
// Returns true if the user confirms, false otherwise
static bool ConfirmShred( const path& inputPath )
{
    cout << "Shred " << absolute( inputPath ) << " ? ( y/n ) ";
    const bool confirm = ( cin.get() == 'y' );

    // Clear any trailing input
    cin.clear();
    cin.ignore( numeric_limits<streamsize>::max(), '\n' );
    return confirm;
}

// Shred a file/folder
static void Shred( const path& inputPath )
{
    Log << absolute( inputPath ) << endl;

    boost::system::error_code ec;
    if ( is_directory( inputPath, ec ) )
    {
        vector<path> dirContents;
        try
        {
            DirectoryIterate( inputPath, dirContents );
        }
        catch ( const filesystem_error& ex )
        {
            LogErrorStream << ex.what() << endl;
            return; // Not able to list the directory's contents. No point in proceeding further.
        }

        for ( const auto& item : dirContents )
        {
            Shred( item );
        }

        ec.clear();
        remove( inputPath, ec ); // Remove the input directory itself.. Will fail if not empty
        if ( ec )
        {
            LogErrorStream << "Failure in removing " << absolute( inputPath ) << " : " << ec.message() << endl;
        }
    }
    else if ( !ec ) // input is a file
    {
        if ( ConfirmShred( inputPath ) )
        {
            ShredFile( inputPath );
        }
        else
        {
            Log << "Skipping " << absolute( inputPath ) << endl;
        }
    }
    else
    {
        LogErrorStream << "Failure in determining if " << absolute( inputPath ) << " is a directory or not : " << ec.message() << endl;
    }
}

int main( int argc, char** argv )
{
    const path defaultLogFilePath = "FileShredderLog.txt";

    if ( argc < 2 )
    {
        cout << "Usage : " << argv[0] << " <input_path> [log_file_path=" << defaultLogFilePath << "]\n";
        return -1;
    }

    const path LogFilePath = ( ( argc >= 3 ) ? path( argv[2] ) :  defaultLogFilePath );
    Log.open( LogFilePath.string() );
    if ( !Log )
    {
        cerr << "Error creating " << absolute( LogFilePath ) << " : " << strerror( errno ) << endl;
        return -1;
    }

    Shred( argv[1] );

    if ( Log )
    {
        if ( LogErrorStream.str().empty() )
        {
            cout << "The program ran without any errors.\n";
        }
        else
        {
            Log << "\nERRORS -:\n\n" << LogErrorStream.str() << endl;
            cout << "There were some errors during the execution of this program !\n\nCheck " << absolute( LogFilePath ) << " for details.\n";
            return -1;
        }
    }
}

I cannot use C++14.

\$\endgroup\$
  • \$\begingroup\$ So, which kind of review are you looking for? \$\endgroup\$ – Morwenn Apr 1 '15 at 11:32
  • \$\begingroup\$ Umm, a general review about anything.. naming conventions, best practices, language usage etc. \$\endgroup\$ – Anmol Singh Jaggi Apr 1 '15 at 11:44
  • 3
    \$\begingroup\$ I am not convinced that ofstream is a low enough level call to achieve what you are trying to do. There is no knowledge in the streams class about how the underlying file system uses its i-nodes and blocks. If you open a file for shredding there is no guarantee that you will be writing to the same blocks as the current file is using for the file (the file system may give you a set of completely different blocks, thus you are writing random data over another set of blocks and your original file blocks are just marked as available for use.). \$\endgroup\$ – Martin York Apr 1 '15 at 16:14
  • \$\begingroup\$ I would bet that this operation is very file system specific and thus you to write your code to perform this at the file system level. \$\endgroup\$ – Martin York Apr 1 '15 at 16:15
  • 1
    \$\begingroup\$ I could be wrong (so it is worth checking). You may look at boost it may have a filesystem library that covers it. But otherwise you will need to do it for every filesystem. There are only a couple of major file systems. \$\endgroup\$ – Martin York Apr 1 '15 at 18:34
4
\$\begingroup\$
const size_t fileNameLength = ( rand() % maxFileNameLength ) + 1;

So 1 out of ever 19 times, you get a 1-character filename. This means that you can expect to repeat filenames once every 684 times. (Actually, slightly more frequently than that: you also get a 2-character filename 1-out-of-19 times which contributes another 1-in-24624)

Don't most OSes have a routine that generates a guaranteed-to-be-unique filename for temporary file purposes? And if not, Boost has boost::filesystem::unique_path().

\$\endgroup\$
  • \$\begingroup\$ There's std::tmpfile() (and std::tmpnam()) although the actual behavior might differ a bit by OS. \$\endgroup\$ – Mario Apr 2 '15 at 6:52
  • \$\begingroup\$ Those have race conditions, as was pointed out in the link that was edited out of my post. \$\endgroup\$ – Snowbody Apr 2 '15 at 13:24
0
\$\begingroup\$

The code after making the changes mentioned in the other answer -:

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#define BOOST_FILESYSTEM_NO_DEPRECATED
#include <boost/filesystem.hpp>

using namespace std;
using namespace boost::filesystem;

static ofstream Log;
static stringstream LogErrorStream;  // Buffer soft errors to output them separately after the informational messages in the log file.

// Iterate through a directory and store everything found ( regular files, directories or any other special files ) in the input container
static void DirectoryIterate( const path& dirPath, vector<path>& dirContents )
{
    if ( is_directory( dirPath ) )
    {
        copy( directory_iterator( dirPath ), directory_iterator(), back_inserter( dirContents ) );
    }
}

// Generates a random file name
static string GenerateRandomFileName()
{
    return unique_path().string();
}

// Renames the input file to a random string
static path RandomRename( const path& inputPath )
{
    const size_t maxRenameAttempts = 10; // Max number of attempts to make in renaming a file.
    // It is possible that the random filename generator returns a name
    // which is same as that of a file in the current directory,
    // or is a reserved filename ( like "com1" on windows )
    size_t attempts = 0;
    while ( attempts < maxRenameAttempts )
    {
        const path newPath = inputPath.parent_path() / GenerateRandomFileName();
        if ( !exists( newPath ) )
        {
            boost::system::error_code ec;
            rename( inputPath, newPath, ec );
            if ( ec ) // Maybe a reserved filename was generated
            {
                LogErrorStream << "Failure in renaming " << absolute( inputPath ) << " to " << absolute( newPath ) << " : " << ec.message() << endl;
            }
            else // No error
            {
                return newPath;
            }
        }
        attempts++;
    }
    return inputPath;
}

// Overwrite the input file with random data
// Returns true if successful, false otherwise
static bool WriteRandomData( const path& inputPath )
{
    boost::system::error_code ec;
    const auto inputFileSize = file_size( inputPath, ec );
    if ( ec )
    {
        LogErrorStream << "Failure in determining the size of " << absolute( inputPath ) << " : "  << ec.message() << endl;
        return false;
    }

    ofstream fout( inputPath.string(), ios::binary );
    if ( !fout )
    {
        LogErrorStream << "Failure in opening " << absolute( inputPath ) << " : " << strerror( errno ) << endl;
        return false;
    }

    vector<unsigned char> buffer( inputFileSize );

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with ASCII 255 and ASCII 0 multiple times alternately
    int iterations = 5;
    while ( iterations -- )
    {
        const unsigned char c = ( iterations & 1 ) ? 255 : 0;
        for ( auto& bufferElement : buffer )
        {
            bufferElement = c;
        }
        fout.seekp( 0, ios::beg );
        fout.write( ( char* )&buffer[0], buffer.size() );
        fout.flush();
    }
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with random characters
    for ( auto& bufferElement : buffer )
    {
        bufferElement = rand() % 128;
    }
    fout.seekp( 0, ios::beg );
    fout.write( ( char* )&buffer[0], buffer.size() );
    fout.flush();
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Overwrite file with null characters
    for ( auto& bufferElement : buffer )
    {
        bufferElement = 0;
    }
    fout.seekp( 0, ios::beg );
    fout.write( ( char* )&buffer[0], buffer.size() );
    fout.flush();
    ////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////
    // Change file size to 0
    fout.close();
    fout.open( inputPath.string(), ios::binary );
    ////////////////////////////////////////////////////////////////////

    fout.close();
    return true;
}

// Shred a file
static void ShredFile( const path& inputPath )
{
    if ( !WriteRandomData( inputPath ) )
    {
        return;
    }

    const path newPath = RandomRename( inputPath );
    boost::system::error_code ec;
    remove( newPath, ec );
    if ( ec )
    {
        LogErrorStream << "Failure in removing " << newPath << " : " << ec.message() << endl;
    }
}

// Confirm shredding a file
// Returns true if the user confirms, false otherwise
static bool ConfirmShred( const path& inputPath )
{
    cout << "Shred " << absolute( inputPath ) << " ? ( y/n ) ";
    const bool confirm = ( cin.get() == 'y' );

    // Clear any trailing input
    cin.clear();
    cin.ignore( numeric_limits<streamsize>::max(), '\n' );
    return confirm;
}

// Shred a file/folder
static void Shred( const path& inputPath )
{
    Log << absolute( inputPath ) << endl;

    boost::system::error_code ec;
    if ( is_directory( inputPath, ec ) )
    {
        vector<path> dirContents;
        try
        {
            DirectoryIterate( inputPath, dirContents );
        }
        catch ( const filesystem_error& ex )
        {
            LogErrorStream << ex.what() << endl;
            return; // Not able to list the directory's contents. No point in proceeding further.
        }

        for ( const auto& item : dirContents )
        {
            Shred( item );
        }

        ec.clear();
        remove( inputPath, ec ); // Remove the input directory itself.. Will fail if not empty
        if ( ec )
        {
            LogErrorStream << "Failure in removing " << absolute( inputPath ) << " : " << ec.message() << endl;
        }
    }
    else if ( !ec ) // input is a file
    {
        if ( ConfirmShred( inputPath ) )
        {
            ShredFile( inputPath );
        }
        else
        {
            Log << "Skipping " << absolute( inputPath ) << endl;
        }
    }
    else
    {
        LogErrorStream << "Failure in determining if " << absolute( inputPath ) << " is a directory or not : " << ec.message() << endl;
    }
}

int main( int argc, char** argv )
{
    const path defaultLogFilePath = "FileShredderLog.txt";

    if ( argc < 2 )
    {
        cout << "Usage : " << argv[0] << " <input_path> [log_file_path=" << defaultLogFilePath << "]\n";
        return -1;
    }

    const path LogFilePath = ( ( argc >= 3 ) ? path( argv[2] ) :  defaultLogFilePath );
    Log.open( LogFilePath.string() );
    if ( !Log )
    {
        cerr << "Error creating " << absolute( LogFilePath ) << " : " << strerror( errno ) << endl;
        return -1;
    }

    Shred( argv[1] );

    if ( Log )
    {
        if ( LogErrorStream.str().empty() )
        {
            cout << "The program ran without any errors.\n";
        }
        else
        {
            Log << "\nERRORS -:\n\n" << LogErrorStream.str() << endl;
            cout << "There were some errors during the execution of this program !\n\nCheck " << absolute( LogFilePath ) << " for details.\n";
            return -1;
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.