While misreading the beginning of Stage I of this classic paper by Ken Thompson, I decided to create program that replicates itself.
Let's say this program is called Replicator.exe. Upon running it once, it will create a new executable, Replicator_1.exe. Upon running either of those executables, a new executable, Replicator_2.exe, will be created.
Some of the things I am interested in:
- De-coupling the Windows dependent logic.
- Naming (I feel like some of my variable and function names are bad).
- Anything else that seems odd or better ways to do this.
Please note that I wrote this with Visual Studio 2012, so I only have access to a limited amount of C++11 features. For your own safety, please do not try to put the contents of main()
into an infinite loop.
This is the main driver:
Driver.cpp
#include "Replicator.h"
#include <algorithm>
#include <fstream>
#include <iterator>
#include <string>
template <class OutIter>
OutIter copy_file (const std::string &filepath, OutIter out, std::ios::openmode open_flags = std::ios::in) ;
int main ()
{
namespace ff = fun_fs ;
const std::string filepath = ff::process_path () ;
const std::string filepath_new = ff::unique_filename (filepath) ;
std::ofstream file (filepath_new, std::ios::binary) ;
copy_file (filepath, std::ostream_iterator <char> (file), std::ios::binary) ;
return 0 ;
}
template <class OutIter>
OutIter copy_file (const std::string &filepath, OutIter out, std::ios::openmode open_flags)
{
std::ifstream file ;
file.open (filepath, open_flags) ;
if (!file.good ()) {
return out ;
}
file.unsetf (std::ios::skipws) ;
auto begin = std::istream_iterator <char> (file) ;
auto end = std::istream_iterator <char> () ;
auto new_out = std::copy (begin, end, out) ;
return new_out ;
}
These are some helper functions:
Replicator.h
#pragma once
#ifndef REPLICATOR_H
#define REPLICATOR_H
#include <string>
namespace fun_fs
{
std::string process_path () ;
std::string unique_filename (std::string filename) ;
}
#endif
Replicator.cpp
#include "Replicator.h"
#include <string>
#include <system_error>
#include <utility>
#include <Windows.h>
namespace fun_fs
{
static std::pair <std::string, std::string> split_file_extension (const std::string &filename) ;
static std::string increment_count (const std::string &filename) ;
}
std::string fun_fs::process_path ()
{
std::string path (500, ' ') ;
DWORD dw = ::GetModuleFileName (nullptr, &path[0], path.size ()) ;
// Do not mistake hidden files for file extensions.
if (dw == 0 || dw == path.size ()) {
std::error_code ec (::GetLastError (), std::system_category ()) ;
throw std::system_error (ec, "::GetModuleFileName () failed.") ;
}
path.resize (dw) ;
return path ;
}
std::string fun_fs::unique_filename (std::string filename)
{
auto file_and_extension = split_file_extension (filename) ;
std::string filename_part = std::move (file_and_extension.first) ;
const std::string file_extension = std::move (file_and_extension.second) ;
do {
filename_part = increment_count (filename_part) ;
filename = filename_part + file_extension ;
} while (::GetFileAttributes (filename.data ()) != INVALID_FILE_ATTRIBUTES) ;
return filename ;
}
// example: "name.txt" -> {"name", ".txt"}
// example: "name" -> {"name", ""}
static std::pair <std::string, std::string> fun_fs::split_file_extension (const std::string &filename)
{
std::string file_extension ;
auto index = filename.rfind ('.') ;
// ignore hidden files
if (index != 0 && index != std::string::npos) {
return std::make_pair (filename.substr (0, index), filename.substr (index)) ;
}
return std::make_pair (filename, "") ;
}
// example: "name" -> "name_1"
// example: "name_2" -> "name_3"
// example: "name_2cool" -> "name_2cool_1"
static std::string fun_fs::increment_count (const std::string &filename)
{
const std::string start_count = "1" ;
auto index = filename.rfind ('_') ;
if (index == (filename.size () - 1)) {
return filename + start_count ;
}
else if (index != std::string::npos) {
const std::string possible_number = filename.substr (index + 1) ;
std::size_t end_of_conversion = 0 ;
try {
int number = std::stoi (possible_number, &end_of_conversion) ;
if (end_of_conversion == possible_number.size ()) {
return filename.substr (0, index + 1) + std::to_string (number + 1) ;
}
}
catch (std::invalid_argument) {
// do nothing...
}
}
return filename + "_" + start_count ;
}