llvm-6502/tools/llvm-ar/llvm-ar.cpp
Reid Spencer a229c5cce7 Final Changes For PR495:
This chagne just renames some sys::Path methods to ensure they are not
misused. The Path documentation now divides methods into two dimensions:
Path/Disk and accessor/mutator. Path accessors and mutators only operate
on the Path object itself without making any disk accesses. Disk accessors
and mutators will also access or modify the file system. Because of the
potentially destructive nature of disk mutators, it was decided that all
such methods should end in the work "Disk" to ensure the user recognizes
that the change will occur on the file system. This patch makes that
change. The method name changes are:

makeReadable        -> makeReadableOnDisk
makeWriteable       -> makeWriteableOnDisk
makeExecutable      -> makeExecutableOnDisk
setStatusInfo       -> setStatusInfoOnDisk
createDirectory     -> createDirectoryOnDisk
createFile          -> createFileOnDisk
createTemporaryFile -> createTemporaryFileOnDisk
destroy             -> eraseFromDisk
rename              -> renamePathOnDisk

These changes pass the Linux Deja Gnu tests.


git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@22354 91177308-0d34-0410-b5e6-96231b3b80d8
2005-07-08 03:08:58 +00:00

730 lines
25 KiB
C++

//===-- llvm-ar.cpp - LLVM archive librarian utility ----------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Builds up (relatively) standard unix archive files (.a) containing LLVM
// bytecode or other files.
//
//===----------------------------------------------------------------------===//
#include "llvm/Module.h"
#include "llvm/Bytecode/Archive.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compressor.h"
#include "llvm/System/Signals.h"
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace llvm;
// Option for compatibility with ASIX, not used but must allow it to be present.
static cl::opt<bool>
X32Option ("X32_64", cl::Hidden,
cl::desc("Ignored option for compatibility with AIX"));
// llvm-ar operation code and modifier flags. This must come first.
static cl::opt<std::string>
Options(cl::Positional, cl::Required, cl::desc("{operation}[modifiers]..."));
// llvm-ar remaining positional arguments.
static cl::list<std::string>
RestOfArgs(cl::Positional, cl::OneOrMore,
cl::desc("[relpos] [count] <archive-file> [members]..."));
// MoreHelp - Provide additional help output explaining the operations and
// modifiers of llvm-ar. This object instructs the CommandLine library
// to print the text of the constructor when the --help option is given.
static cl::extrahelp MoreHelp(
"\nOPERATIONS:\n"
" d[NsS] - delete file(s) from the archive\n"
" m[abiSs] - move file(s) in the archive\n"
" p[kN] - print file(s) found in the archive\n"
" q[ufsS] - quick append file(s) to the archive\n"
" r[abfiuzRsS] - replace or insert file(s) into the archive\n"
" t - display contents of archive\n"
" x[No] - extract file(s) from the archive\n"
"\nMODIFIERS (operation specific):\n"
" [a] - put file(s) after [relpos]\n"
" [b] - put file(s) before [relpos] (same as [i])\n"
" [f] - truncate inserted file names\n"
" [i] - put file(s) before [relpos] (same as [b])\n"
" [k] - always print bytecode files (default is to skip them)\n"
" [N] - use instance [count] of name\n"
" [o] - preserve original dates\n"
" [P] - use full path names when matching\n"
" [R] - recurse through directories when inserting\n"
" [s] - create an archive index (cf. ranlib)\n"
" [S] - do not build a symbol table\n"
" [u] - update only files newer than archive contents\n"
" [z] - compress files before inserting/extracting\n"
"\nMODIFIERS (generic):\n"
" [c] - do not warn if the library had to be created\n"
" [v] - be verbose about actions taken\n"
" [V] - be *really* verbose about actions taken\n"
);
// This enumeration delineates the kinds of operations on an archive
// that are permitted.
enum ArchiveOperation {
NoOperation, ///< An operation hasn't been specified
Print, ///< Print the contents of the archive
Delete, ///< Delete the specified members
Move, ///< Move members to end or as given by {a,b,i} modifiers
QuickAppend, ///< Quickly append to end of archive
ReplaceOrInsert, ///< Replace or Insert members
DisplayTable, ///< Display the table of contents
Extract, ///< Extract files back to file system
};
// Modifiers to follow operation to vary behavior
bool AddAfter = false; ///< 'a' modifier
bool AddBefore = false; ///< 'b' modifier
bool Create = false; ///< 'c' modifier
bool TruncateNames = false; ///< 'f' modifier
bool InsertBefore = false; ///< 'i' modifier
bool DontSkipBytecode = false; ///< 'k' modifier
bool UseCount = false; ///< 'N' modifier
bool OriginalDates = false; ///< 'o' modifier
bool FullPath = false; ///< 'P' modifier
bool RecurseDirectories = false; ///< 'R' modifier
bool SymTable = true; ///< 's' & 'S' modifiers
bool OnlyUpdate = false; ///< 'u' modifier
bool Verbose = false; ///< 'v' modifier
bool ReallyVerbose = false; ///< 'V' modifier
bool Compression = false; ///< 'z' modifier
// Relative Positional Argument (for insert/move). This variable holds
// the name of the archive member to which the 'a', 'b' or 'i' modifier
// refers. Only one of 'a', 'b' or 'i' can be specified so we only need
// one variable.
std::string RelPos;
// Select which of multiple entries in the archive with the same name should be
// used (specified with -N) for the delete and extract operations.
int Count = 1;
// This variable holds the name of the archive file as given on the
// command line.
std::string ArchiveName;
// This variable holds the list of member files to proecess, as given
// on the command line.
std::vector<std::string> Members;
// This variable holds the (possibly expanded) list of path objects that
// correspond to files we will
std::set<sys::Path> Paths;
// The Archive object to which all the editing operations will be sent.
Archive* TheArchive = 0;
// getRelPos - Extract the member filename from the command line for
// the [relpos] argument associated with a, b, and i modifiers
void getRelPos() {
if(RestOfArgs.size() > 0) {
RelPos = RestOfArgs[0];
RestOfArgs.erase(RestOfArgs.begin());
}
else
throw "Expected [relpos] for a, b, or i modifier";
}
// getCount - Extract the [count] argument associated with the N modifier
// from the command line and check its value.
void getCount() {
if(RestOfArgs.size() > 0) {
Count = atoi(RestOfArgs[0].c_str());
RestOfArgs.erase(RestOfArgs.begin());
}
else
throw "Expected [count] value with N modifier";
// Non-positive counts are not allowed
if (Count < 1)
throw "Invalid [count] value (not a positive integer)";
}
// getArchive - Get the archive file name from the command line
void getArchive() {
if(RestOfArgs.size() > 0) {
ArchiveName = RestOfArgs[0];
RestOfArgs.erase(RestOfArgs.begin());
}
else
throw "An archive name must be specified.";
}
// getMembers - Copy over remaining items in RestOfArgs to our Members vector
// This is just for clarity.
void getMembers() {
if(RestOfArgs.size() > 0)
Members = std::vector<std::string>(RestOfArgs);
}
// parseCommandLine - Parse the command line options as presented and return the
// operation specified. Process all modifiers and check to make sure that
// constraints on modifier/operation pairs have not been violated.
ArchiveOperation parseCommandLine() {
// Keep track of number of operations. We can only specify one
// per execution.
unsigned NumOperations = 0;
// Keep track of the number of positional modifiers (a,b,i). Only
// one can be specified.
unsigned NumPositional = 0;
// Keep track of which operation was requested
ArchiveOperation Operation = NoOperation;
for(unsigned i=0; i<Options.size(); ++i) {
switch(Options[i]) {
case 'd': ++NumOperations; Operation = Delete; break;
case 'm': ++NumOperations; Operation = Move ; break;
case 'p': ++NumOperations; Operation = Print; break;
case 'r': ++NumOperations; Operation = ReplaceOrInsert; break;
case 't': ++NumOperations; Operation = DisplayTable; break;
case 'x': ++NumOperations; Operation = Extract; break;
case 'c': Create = true; break;
case 'f': TruncateNames = true; break;
case 'k': DontSkipBytecode = true; break;
case 'l': /* accepted but unused */ break;
case 'o': OriginalDates = true; break;
case 'P': FullPath = true; break;
case 'R': RecurseDirectories = true; break;
case 's': SymTable = true; break;
case 'S': SymTable = false; break;
case 'u': OnlyUpdate = true; break;
case 'v': Verbose = true; break;
case 'V': Verbose = ReallyVerbose = true; break;
case 'z': Compression = true; break;
case 'a':
getRelPos();
AddAfter = true;
NumPositional++;
break;
case 'b':
getRelPos();
AddBefore = true;
NumPositional++;
break;
case 'i':
getRelPos();
InsertBefore = true;
NumPositional++;
break;
case 'N':
getCount();
UseCount = true;
break;
default:
cl::PrintHelpMessage();
}
}
// At this point, the next thing on the command line must be
// the archive name.
getArchive();
// Everything on the command line at this point is a member.
getMembers();
// Perform various checks on the operation/modifier specification
// to make sure we are dealing with a legal request.
if (NumOperations == 0)
throw "You must specify at least one of the operations";
if (NumOperations > 1)
throw "Only one operation may be specified";
if (NumPositional > 1)
throw "You may only specify one of a, b, and i modifiers";
if (AddAfter || AddBefore || InsertBefore)
if (Operation != Move && Operation != ReplaceOrInsert)
throw "The 'a', 'b' and 'i' modifiers can only be specified with "
"the 'm' or 'r' operations";
if (RecurseDirectories && Operation != ReplaceOrInsert)
throw "The 'R' modifiers is only applicabe to the 'r' operation";
if (OriginalDates && Operation != Extract)
throw "The 'o' modifier is only applicable to the 'x' operation";
if (TruncateNames && Operation!=QuickAppend && Operation!=ReplaceOrInsert)
throw "The 'f' modifier is only applicable to the 'q' and 'r' operations";
if (OnlyUpdate && Operation != ReplaceOrInsert)
throw "The 'u' modifier is only applicable to the 'r' operation";
if (Compression && Operation!=ReplaceOrInsert && Operation!=Extract)
throw "The 'z' modifier is only applicable to the 'r' and 'x' operations";
if (Count > 1 && Members.size() > 1)
throw "Only one member name may be specified with the 'N' modifier";
// Return the parsed operation to the caller
return Operation;
}
// recurseDirectories - Implements the "R" modifier. This function scans through
// the Paths vector (built by buildPaths, below) and replaces any directories it
// finds with all the files in that directory (recursively). It uses the
// sys::Path::getDirectoryContent method to perform the actual directory scans.
std::set<sys::Path> recurseDirectories(const sys::Path& path) {
assert(path.isDirectory() && "Oops, can't recurse a file");
std::set<sys::Path> result;
if (RecurseDirectories) {
std::set<sys::Path> content;
path.getDirectoryContents(content);
for (std::set<sys::Path>::iterator I = content.begin(), E = content.end();
I != E; ++I) {
if (I->isDirectory()) {
std::set<sys::Path> moreResults = recurseDirectories(*I);
result.insert(moreResults.begin(), moreResults.end());
} else {
result.insert(*I);
}
}
}
return result;
}
// buildPaths - Convert the strings in the Members vector to sys::Path objects
// and make sure they are valid and exist exist. This check is only needed for
// the operations that add/replace files to the archive ('q' and 'r')
void buildPaths(bool checkExistence = true) {
for (unsigned i = 0; i < Members.size(); i++) {
sys::Path aPath;
if (!aPath.set(Members[i]))
throw std::string("File member name invalid: ") + Members[i];
if (checkExistence) {
if (!aPath.exists())
throw std::string("File does not exist: ") + Members[i];
sys::Path::StatusInfo si;
aPath.getStatusInfo(si);
if (si.isDir) {
std::set<sys::Path> dirpaths = recurseDirectories(aPath);
Paths.insert(dirpaths.begin(),dirpaths.end());
} else {
Paths.insert(aPath);
}
} else {
Paths.insert(aPath);
}
}
}
// printSymbolTable - print out the archive's symbol table.
void printSymbolTable() {
std::cout << "\nArchive Symbol Table:\n";
const Archive::SymTabType& symtab = TheArchive->getSymbolTable();
for (Archive::SymTabType::const_iterator I=symtab.begin(), E=symtab.end();
I != E; ++I ) {
unsigned offset = TheArchive->getFirstFileOffset() + I->second;
std::cout << " " << std::setw(9) << offset << "\t" << I->first <<"\n";
}
}
// doPrint - Implements the 'p' operation. This function traverses the archive
// looking for members that match the path list. It is careful to uncompress
// things that should be and to skip bytecode files unless the 'k' modifier was
// given.
void doPrint() {
buildPaths(false);
unsigned countDown = Count;
for (Archive::iterator I = TheArchive->begin(), E = TheArchive->end();
I != E; ++I ) {
if (Paths.empty() ||
(std::find(Paths.begin(), Paths.end(), I->getPath()) != Paths.end())) {
if (countDown == 1) {
const char* data = reinterpret_cast<const char*>(I->getData());
// Skip things that don't make sense to print
if (I->isLLVMSymbolTable() || I->isSVR4SymbolTable() ||
I->isBSD4SymbolTable() || (!DontSkipBytecode &&
(I->isBytecode() || I->isCompressedBytecode())))
continue;
if (Verbose)
std::cout << "Printing " << I->getPath().toString() << "\n";
if (I->isCompressedBytecode())
Compressor::decompressToStream(data+4,I->getSize()-4,std::cout);
else if (I->isCompressed()) {
Compressor::decompressToStream(data,I->getSize(),std::cout);
} else {
unsigned len = I->getSize();
std::cout.write(data, len);
}
} else {
countDown--;
}
}
}
}
// putMode - utility function for printing out the file mode when the 't'
// operation is in verbose mode.
void printMode(unsigned mode) {
if (mode & 004)
std::cout << "r";
else
std::cout << "-";
if (mode & 002)
std::cout << "w";
else
std::cout << "-";
if (mode & 001)
std::cout << "x";
else
std::cout << "-";
}
// doDisplayTable - Implement the 't' operation. This function prints out just
// the file names of each of the members. However, if verbose mode is requested
// ('v' modifier) then the file type, permission mode, user, group, size, and
// modification time are also printed.
void doDisplayTable() {
buildPaths(false);
for (Archive::iterator I = TheArchive->begin(), E = TheArchive->end();
I != E; ++I ) {
if (Paths.empty() ||
(std::find(Paths.begin(), Paths.end(), I->getPath()) != Paths.end())) {
if (Verbose) {
// FIXME: Output should be this format:
// Zrw-r--r-- 500/ 500 525 Nov 8 17:42 2004 Makefile
if (I->isBytecode())
std::cout << "b";
else if (I->isCompressedBytecode())
std::cout << "B";
else if (I->isCompressed())
std::cout << "Z";
else
std::cout << " ";
unsigned mode = I->getMode();
printMode((mode >> 6) & 007);
printMode((mode >> 3) & 007);
printMode(mode & 007);
std::cout << " " << std::setw(4) << I->getUser();
std::cout << "/" << std::setw(4) << I->getGroup();
std::cout << " " << std::setw(8) << I->getSize();
std::cout << " " << std::setw(20) <<
I->getModTime().toString().substr(4);
std::cout << " " << I->getPath().toString() << "\n";
} else {
std::cout << I->getPath().toString() << "\n";
}
}
}
if (ReallyVerbose)
printSymbolTable();
}
// doExtract - Implement the 'x' operation. This function extracts files back to
// the file system, making sure to uncompress any that were compressed.
void doExtract() {
buildPaths(false);
unsigned countDown = Count;
for (Archive::iterator I = TheArchive->begin(), E = TheArchive->end();
I != E; ++I ) {
if (Paths.empty() ||
(std::find(Paths.begin(), Paths.end(), I->getPath()) != Paths.end())) {
// Make sure the intervening directories are created
if (I->hasPath()) {
sys::Path dirs(I->getPath());
dirs.eraseComponent();
dirs.createDirectoryOnDisk(/*create_parents=*/true);
}
// Open up a file stream for writing
std::ios::openmode io_mode = std::ios::out | std::ios::trunc |
std::ios::binary;
std::ofstream file(I->getPath().c_str(), io_mode);
// Get the data and its length
const char* data = reinterpret_cast<const char*>(I->getData());
unsigned len = I->getSize();
// Write the data, making sure to uncompress things first
if (I->isCompressed()) {
Compressor::decompressToStream(data,len,file);
} else {
file.write(data,len);
}
file.close();
// If we're supposed to retain the original modification times, etc. do so
// now.
if (OriginalDates)
I->getPath().setStatusInfoOnDisk(I->getStatusInfo());
}
}
}
// doDelete - Implement the delete operation. This function deletes zero or more
// members from the archive. Note that if the count is specified, there should
// be no more than one path in the Paths list or else this algorithm breaks.
// That check is enforced in parseCommandLine (above).
void doDelete() {
buildPaths(false);
if (Paths.empty()) return;
unsigned countDown = Count;
for (Archive::iterator I = TheArchive->begin(), E = TheArchive->end();
I != E; ) {
if (std::find(Paths.begin(), Paths.end(), I->getPath()) != Paths.end()) {
if (countDown == 1) {
Archive::iterator J = I;
++I;
TheArchive->erase(J);
} else
countDown--;
} else {
++I;
}
}
// We're done editting, reconstruct the archive.
TheArchive->writeToDisk(SymTable,TruncateNames,Compression);
if (ReallyVerbose)
printSymbolTable();
}
// doMore - Implement the move operation. This function re-arranges just the
// order of the archive members so that when the archive is written the move
// of the members is accomplished. Note the use of the RelPos variable to
// determine where the items should be moved to.
void doMove() {
buildPaths(false);
// By default and convention the place to move members to is the end of the
// archive.
Archive::iterator moveto_spot = TheArchive->end();
// However, if the relative positioning modifiers were used, we need to scan
// the archive to find the member in question. If we don't find it, its no
// crime, we just move to the end.
if (AddBefore || InsertBefore || AddAfter) {
for (Archive::iterator I = TheArchive->begin(), E= TheArchive->end();
I != E; ++I ) {
if (RelPos == I->getPath().toString()) {
if (AddAfter) {
moveto_spot = I;
moveto_spot++;
} else {
moveto_spot = I;
}
break;
}
}
}
// Keep a list of the paths remaining to be moved
std::set<sys::Path> remaining(Paths);
// Scan the archive again, this time looking for the members to move to the
// moveto_spot.
for (Archive::iterator I = TheArchive->begin(), E= TheArchive->end();
I != E && !remaining.empty(); ++I ) {
std::set<sys::Path>::iterator found =
std::find(remaining.begin(),remaining.end(),I->getPath());
if (found != remaining.end()) {
if (I != moveto_spot)
TheArchive->splice(moveto_spot,*TheArchive,I);
remaining.erase(found);
}
}
// We're done editting, reconstruct the archive.
TheArchive->writeToDisk(SymTable,TruncateNames,Compression);
if (ReallyVerbose)
printSymbolTable();
}
// doQuickAppend - Implements the 'q' operation. This function just
// indiscriminantly adds the members to the archive and rebuilds it.
void doQuickAppend() {
// Get the list of paths to append.
buildPaths(true);
if (Paths.empty()) return;
// Append them quickly.
for (std::set<sys::Path>::iterator PI = Paths.begin(), PE = Paths.end();
PI != PE; ++PI) {
TheArchive->addFileBefore(*PI,TheArchive->end());
}
// We're done editting, reconstruct the archive.
TheArchive->writeToDisk(SymTable,TruncateNames,Compression);
if (ReallyVerbose)
printSymbolTable();
}
// doReplaceOrInsert - Implements the 'r' operation. This function will replace
// any existing files or insert new ones into the archive.
void doReplaceOrInsert() {
// Build the list of files to be added/replaced.
buildPaths(true);
if (Paths.empty()) return;
// Keep track of the paths that remain to be inserted.
std::set<sys::Path> remaining(Paths);
// Default the insertion spot to the end of the archive
Archive::iterator insert_spot = TheArchive->end();
// Iterate over the archive contents
for (Archive::iterator I = TheArchive->begin(), E = TheArchive->end();
I != E && !remaining.empty(); ++I ) {
// Determine if this archive member matches one of the paths we're trying
// to replace.
std::set<sys::Path>::iterator found = remaining.end();
for (std::set<sys::Path>::iterator RI = remaining.begin(),
RE = remaining.end(); RI != RE; ++RI ) {
std::string compare(RI->toString());
if (TruncateNames && compare.length() > 15) {
const char* nm = compare.c_str();
unsigned len = compare.length();
size_t slashpos = compare.rfind('/');
if (slashpos != std::string::npos) {
nm += slashpos + 1;
len -= slashpos +1;
}
if (len > 15)
len = 15;
compare.assign(nm,len);
}
if (compare == I->getPath().toString()) {
found = RI;
break;
}
}
if (found != remaining.end()) {
sys::Path::StatusInfo si;
found->getStatusInfo(si);
if (si.isDir) {
if (OnlyUpdate) {
// Replace the item only if it is newer.
if (si.modTime > I->getModTime())
I->replaceWith(*found);
} else {
// Replace the item regardless of time stamp
I->replaceWith(*found);
}
} else {
// We purposefully ignore directories.
}
// Remove it from our "to do" list
remaining.erase(found);
}
// Determine if this is the place where we should insert
if ((AddBefore || InsertBefore) && (RelPos == I->getPath().toString()))
insert_spot = I;
else if (AddAfter && (RelPos == I->getPath().toString())) {
insert_spot = I;
insert_spot++;
}
}
// If we didn't replace all the members, some will remain and need to be
// inserted at the previously computed insert-spot.
if (!remaining.empty()) {
for (std::set<sys::Path>::iterator PI = remaining.begin(),
PE = remaining.end(); PI != PE; ++PI) {
TheArchive->addFileBefore(*PI,insert_spot);
}
}
// We're done editting, reconstruct the archive.
TheArchive->writeToDisk(SymTable,TruncateNames,Compression);
if (ReallyVerbose)
printSymbolTable();
}
// main - main program for llvm-ar .. see comments in the code
int main(int argc, char **argv) {
// Have the command line options parsed and handle things
// like --help and --version.
cl::ParseCommandLineOptions(argc, argv,
" LLVM Archiver (llvm-ar)\n\n"
" This program archives bytecode files into single libraries\n"
);
// Print a stack trace if we signal out.
sys::PrintStackTraceOnErrorSignal();
int exitCode = 0;
// Make sure we don't exit with "unhandled exception".
try {
// Do our own parsing of the command line because the CommandLine utility
// can't handle the grouped positional parameters without a dash.
ArchiveOperation Operation = parseCommandLine();
// Check the path name of the archive
sys::Path ArchivePath;
if (!ArchivePath.set(ArchiveName))
throw std::string("Archive name invalid: ") + ArchiveName;
// Create or open the archive object.
if (!ArchivePath.exists()) {
// Produce a warning if we should and we're creating the archive
if (!Create)
std::cerr << argv[0] << ": creating " << ArchivePath.toString() << "\n";
TheArchive = Archive::CreateEmpty(ArchivePath);
} else {
std::string Error;
TheArchive = Archive::OpenAndLoad(ArchivePath, &Error);
if (TheArchive == 0) {
std::cerr << argv[0] << ": error loading '" << ArchivePath << "': "
<< Error << "!\n";
return 1;
}
}
// Make sure we're not fooling ourselves.
assert(TheArchive && "Unable to instantiate the archive");
// Make sure we clean up the archive even on failure.
std::auto_ptr<Archive> AutoArchive(TheArchive);
// Perform the operation
switch (Operation) {
case Print: doPrint(); break;
case Delete: doDelete(); break;
case Move: doMove(); break;
case QuickAppend: /* FALL THROUGH */
case ReplaceOrInsert: doReplaceOrInsert(); break;
case DisplayTable: doDisplayTable(); break;
case Extract: doExtract(); break;
case NoOperation:
std::cerr << argv[0] << ": No operation was selected.\n";
break;
}
} catch (const char*msg) {
// These errors are usage errors, thrown only by the various checks in the
// code above.
std::cerr << argv[0] << ": " << msg << "\n\n";
cl::PrintHelpMessage();
exitCode = 1;
} catch (const std::string& msg) {
// These errors are thrown by LLVM libraries (e.g. lib System) and represent
// a more serious error so we bump the exitCode and don't print the usage.
std::cerr << argv[0] << ": " << msg << "\n";
exitCode = 2;
} catch (...) {
// This really shouldn't happen, but just in case ....
std::cerr << argv[0] << ": An unexpected unknown exception occurred.\n";
exitCode = 3;
}
// Return result code back to operating system.
return exitCode;
}