1 //===-- llvm-ar.cpp - LLVM archive librarian utility ----------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // Builds up (relatively) standard unix archive files (.a) containing LLVM
11 // bitcode or other files.
13 //===----------------------------------------------------------------------===//
15 #include "llvm/Module.h"
16 #include "llvm/Bitcode/Archive.h"
17 #include "llvm/Support/CommandLine.h"
18 #include "llvm/Support/ManagedStatic.h"
19 #include "llvm/Support/PrettyStackTrace.h"
20 #include "llvm/System/Signals.h"
27 // Option for compatibility with AIX, not used but must allow it to be present.
29 X32Option ("X32_64", cl::Hidden
,
30 cl::desc("Ignored option for compatibility with AIX"));
32 // llvm-ar operation code and modifier flags. This must come first.
33 static cl::opt
<std::string
>
34 Options(cl::Positional
, cl::Required
, cl::desc("{operation}[modifiers]..."));
36 // llvm-ar remaining positional arguments.
37 static cl::list
<std::string
>
38 RestOfArgs(cl::Positional
, cl::OneOrMore
,
39 cl::desc("[relpos] [count] <archive-file> [members]..."));
41 // MoreHelp - Provide additional help output explaining the operations and
42 // modifiers of llvm-ar. This object instructs the CommandLine library
43 // to print the text of the constructor when the --help option is given.
44 static cl::extrahelp
MoreHelp(
46 " d[NsS] - delete file(s) from the archive\n"
47 " m[abiSs] - move file(s) in the archive\n"
48 " p[kN] - print file(s) found in the archive\n"
49 " q[ufsS] - quick append file(s) to the archive\n"
50 " r[abfiuzRsS] - replace or insert file(s) into the archive\n"
51 " t - display contents of archive\n"
52 " x[No] - extract file(s) from the archive\n"
53 "\nMODIFIERS (operation specific):\n"
54 " [a] - put file(s) after [relpos]\n"
55 " [b] - put file(s) before [relpos] (same as [i])\n"
56 " [f] - truncate inserted file names\n"
57 " [i] - put file(s) before [relpos] (same as [b])\n"
58 " [k] - always print bitcode files (default is to skip them)\n"
59 " [N] - use instance [count] of name\n"
60 " [o] - preserve original dates\n"
61 " [P] - use full path names when matching\n"
62 " [R] - recurse through directories when inserting\n"
63 " [s] - create an archive index (cf. ranlib)\n"
64 " [S] - do not build a symbol table\n"
65 " [u] - update only files newer than archive contents\n"
66 " [z] - compress files before inserting/extracting\n"
67 "\nMODIFIERS (generic):\n"
68 " [c] - do not warn if the library had to be created\n"
69 " [v] - be verbose about actions taken\n"
70 " [V] - be *really* verbose about actions taken\n"
73 // This enumeration delineates the kinds of operations on an archive
74 // that are permitted.
75 enum ArchiveOperation
{
76 NoOperation
, ///< An operation hasn't been specified
77 Print
, ///< Print the contents of the archive
78 Delete
, ///< Delete the specified members
79 Move
, ///< Move members to end or as given by {a,b,i} modifiers
80 QuickAppend
, ///< Quickly append to end of archive
81 ReplaceOrInsert
, ///< Replace or Insert members
82 DisplayTable
, ///< Display the table of contents
83 Extract
///< Extract files back to file system
86 // Modifiers to follow operation to vary behavior
87 bool AddAfter
= false; ///< 'a' modifier
88 bool AddBefore
= false; ///< 'b' modifier
89 bool Create
= false; ///< 'c' modifier
90 bool TruncateNames
= false; ///< 'f' modifier
91 bool InsertBefore
= false; ///< 'i' modifier
92 bool DontSkipBitcode
= false; ///< 'k' modifier
93 bool UseCount
= false; ///< 'N' modifier
94 bool OriginalDates
= false; ///< 'o' modifier
95 bool FullPath
= false; ///< 'P' modifier
96 bool RecurseDirectories
= false; ///< 'R' modifier
97 bool SymTable
= true; ///< 's' & 'S' modifiers
98 bool OnlyUpdate
= false; ///< 'u' modifier
99 bool Verbose
= false; ///< 'v' modifier
100 bool ReallyVerbose
= false; ///< 'V' modifier
101 bool Compression
= false; ///< 'z' modifier
103 // Relative Positional Argument (for insert/move). This variable holds
104 // the name of the archive member to which the 'a', 'b' or 'i' modifier
105 // refers. Only one of 'a', 'b' or 'i' can be specified so we only need
109 // Select which of multiple entries in the archive with the same name should be
110 // used (specified with -N) for the delete and extract operations.
113 // This variable holds the name of the archive file as given on the
115 std::string ArchiveName
;
117 // This variable holds the list of member files to proecess, as given
118 // on the command line.
119 std::vector
<std::string
> Members
;
121 // This variable holds the (possibly expanded) list of path objects that
122 // correspond to files we will
123 std::set
<sys::Path
> Paths
;
125 // The Archive object to which all the editing operations will be sent.
126 Archive
* TheArchive
= 0;
128 // getRelPos - Extract the member filename from the command line for
129 // the [relpos] argument associated with a, b, and i modifiers
131 if(RestOfArgs
.size() > 0) {
132 RelPos
= RestOfArgs
[0];
133 RestOfArgs
.erase(RestOfArgs
.begin());
136 throw "Expected [relpos] for a, b, or i modifier";
139 // getCount - Extract the [count] argument associated with the N modifier
140 // from the command line and check its value.
142 if(RestOfArgs
.size() > 0) {
143 Count
= atoi(RestOfArgs
[0].c_str());
144 RestOfArgs
.erase(RestOfArgs
.begin());
147 throw "Expected [count] value with N modifier";
149 // Non-positive counts are not allowed
151 throw "Invalid [count] value (not a positive integer)";
154 // getArchive - Get the archive file name from the command line
156 if(RestOfArgs
.size() > 0) {
157 ArchiveName
= RestOfArgs
[0];
158 RestOfArgs
.erase(RestOfArgs
.begin());
161 throw "An archive name must be specified.";
164 // getMembers - Copy over remaining items in RestOfArgs to our Members vector
165 // This is just for clarity.
167 if(RestOfArgs
.size() > 0)
168 Members
= std::vector
<std::string
>(RestOfArgs
);
171 // parseCommandLine - Parse the command line options as presented and return the
172 // operation specified. Process all modifiers and check to make sure that
173 // constraints on modifier/operation pairs have not been violated.
174 ArchiveOperation
parseCommandLine() {
176 // Keep track of number of operations. We can only specify one
178 unsigned NumOperations
= 0;
180 // Keep track of the number of positional modifiers (a,b,i). Only
181 // one can be specified.
182 unsigned NumPositional
= 0;
184 // Keep track of which operation was requested
185 ArchiveOperation Operation
= NoOperation
;
187 for(unsigned i
=0; i
<Options
.size(); ++i
) {
189 case 'd': ++NumOperations
; Operation
= Delete
; break;
190 case 'm': ++NumOperations
; Operation
= Move
; break;
191 case 'p': ++NumOperations
; Operation
= Print
; break;
192 case 'q': ++NumOperations
; Operation
= QuickAppend
; break;
193 case 'r': ++NumOperations
; Operation
= ReplaceOrInsert
; break;
194 case 't': ++NumOperations
; Operation
= DisplayTable
; break;
195 case 'x': ++NumOperations
; Operation
= Extract
; break;
196 case 'c': Create
= true; break;
197 case 'f': TruncateNames
= true; break;
198 case 'k': DontSkipBitcode
= true; break;
199 case 'l': /* accepted but unused */ break;
200 case 'o': OriginalDates
= true; break;
201 case 'P': FullPath
= true; break;
202 case 'R': RecurseDirectories
= true; break;
203 case 's': SymTable
= true; break;
204 case 'S': SymTable
= false; break;
205 case 'u': OnlyUpdate
= true; break;
206 case 'v': Verbose
= true; break;
207 case 'V': Verbose
= ReallyVerbose
= true; break;
208 case 'z': Compression
= true; break;
229 cl::PrintHelpMessage();
233 // At this point, the next thing on the command line must be
237 // Everything on the command line at this point is a member.
240 // Perform various checks on the operation/modifier specification
241 // to make sure we are dealing with a legal request.
242 if (NumOperations
== 0)
243 throw "You must specify at least one of the operations";
244 if (NumOperations
> 1)
245 throw "Only one operation may be specified";
246 if (NumPositional
> 1)
247 throw "You may only specify one of a, b, and i modifiers";
248 if (AddAfter
|| AddBefore
|| InsertBefore
)
249 if (Operation
!= Move
&& Operation
!= ReplaceOrInsert
)
250 throw "The 'a', 'b' and 'i' modifiers can only be specified with "
251 "the 'm' or 'r' operations";
252 if (RecurseDirectories
&& Operation
!= ReplaceOrInsert
)
253 throw "The 'R' modifiers is only applicabe to the 'r' operation";
254 if (OriginalDates
&& Operation
!= Extract
)
255 throw "The 'o' modifier is only applicable to the 'x' operation";
256 if (TruncateNames
&& Operation
!=QuickAppend
&& Operation
!=ReplaceOrInsert
)
257 throw "The 'f' modifier is only applicable to the 'q' and 'r' operations";
258 if (OnlyUpdate
&& Operation
!= ReplaceOrInsert
)
259 throw "The 'u' modifier is only applicable to the 'r' operation";
260 if (Compression
&& Operation
!=ReplaceOrInsert
&& Operation
!=Extract
)
261 throw "The 'z' modifier is only applicable to the 'r' and 'x' operations";
262 if (Count
> 1 && Members
.size() > 1)
263 throw "Only one member name may be specified with the 'N' modifier";
265 // Return the parsed operation to the caller
269 // recurseDirectories - Implements the "R" modifier. This function scans through
270 // the Paths vector (built by buildPaths, below) and replaces any directories it
271 // finds with all the files in that directory (recursively). It uses the
272 // sys::Path::getDirectoryContent method to perform the actual directory scans.
274 recurseDirectories(const sys::Path
& path
,
275 std::set
<sys::Path
>& result
, std::string
* ErrMsg
) {
277 if (RecurseDirectories
) {
278 std::set
<sys::Path
> content
;
279 if (path
.getDirectoryContents(content
, ErrMsg
))
282 for (std::set
<sys::Path
>::iterator I
= content
.begin(), E
= content
.end();
284 // Make sure it exists and is a directory
285 sys::PathWithStatus
PwS(*I
);
286 const sys::FileStatus
*Status
= PwS
.getFileStatus(false, ErrMsg
);
290 std::set
<sys::Path
> moreResults
;
291 if (recurseDirectories(*I
, moreResults
, ErrMsg
))
293 result
.insert(moreResults
.begin(), moreResults
.end());
302 // buildPaths - Convert the strings in the Members vector to sys::Path objects
303 // and make sure they are valid and exist exist. This check is only needed for
304 // the operations that add/replace files to the archive ('q' and 'r')
305 bool buildPaths(bool checkExistence
, std::string
* ErrMsg
) {
306 for (unsigned i
= 0; i
< Members
.size(); i
++) {
308 if (!aPath
.set(Members
[i
]))
309 throw std::string("File member name invalid: ") + Members
[i
];
310 if (checkExistence
) {
312 throw std::string("File does not exist: ") + Members
[i
];
314 sys::PathWithStatus
PwS(aPath
);
315 const sys::FileStatus
*si
= PwS
.getFileStatus(false, &Err
);
319 std::set
<sys::Path
> dirpaths
;
320 if (recurseDirectories(aPath
, dirpaths
, ErrMsg
))
322 Paths
.insert(dirpaths
.begin(),dirpaths
.end());
333 // printSymbolTable - print out the archive's symbol table.
334 void printSymbolTable() {
335 std::cout
<< "\nArchive Symbol Table:\n";
336 const Archive::SymTabType
& symtab
= TheArchive
->getSymbolTable();
337 for (Archive::SymTabType::const_iterator I
=symtab
.begin(), E
=symtab
.end();
339 unsigned offset
= TheArchive
->getFirstFileOffset() + I
->second
;
340 std::cout
<< " " << std::setw(9) << offset
<< "\t" << I
->first
<<"\n";
344 // doPrint - Implements the 'p' operation. This function traverses the archive
345 // looking for members that match the path list. It is careful to uncompress
346 // things that should be and to skip bitcode files unless the 'k' modifier was
348 bool doPrint(std::string
* ErrMsg
) {
349 if (buildPaths(false, ErrMsg
))
351 unsigned countDown
= Count
;
352 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
355 (std::find(Paths
.begin(), Paths
.end(), I
->getPath()) != Paths
.end())) {
356 if (countDown
== 1) {
357 const char* data
= reinterpret_cast<const char*>(I
->getData());
359 // Skip things that don't make sense to print
360 if (I
->isLLVMSymbolTable() || I
->isSVR4SymbolTable() ||
361 I
->isBSD4SymbolTable() || (!DontSkipBitcode
&& I
->isBitcode()))
365 std::cout
<< "Printing " << I
->getPath().toString() << "\n";
367 unsigned len
= I
->getSize();
368 std::cout
.write(data
, len
);
377 // putMode - utility function for printing out the file mode when the 't'
378 // operation is in verbose mode.
380 printMode(unsigned mode
) {
395 // doDisplayTable - Implement the 't' operation. This function prints out just
396 // the file names of each of the members. However, if verbose mode is requested
397 // ('v' modifier) then the file type, permission mode, user, group, size, and
398 // modification time are also printed.
400 doDisplayTable(std::string
* ErrMsg
) {
401 if (buildPaths(false, ErrMsg
))
403 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
406 (std::find(Paths
.begin(), Paths
.end(), I
->getPath()) != Paths
.end())) {
408 // FIXME: Output should be this format:
409 // Zrw-r--r-- 500/ 500 525 Nov 8 17:42 2004 Makefile
412 else if (I
->isCompressed())
416 unsigned mode
= I
->getMode();
417 printMode((mode
>> 6) & 007);
418 printMode((mode
>> 3) & 007);
419 printMode(mode
& 007);
420 std::cout
<< " " << std::setw(4) << I
->getUser();
421 std::cout
<< "/" << std::setw(4) << I
->getGroup();
422 std::cout
<< " " << std::setw(8) << I
->getSize();
423 std::cout
<< " " << std::setw(20) <<
424 I
->getModTime().toString().substr(4);
425 std::cout
<< " " << I
->getPath().toString() << "\n";
427 std::cout
<< I
->getPath().toString() << "\n";
436 // doExtract - Implement the 'x' operation. This function extracts files back to
437 // the file system, making sure to uncompress any that were compressed
439 doExtract(std::string
* ErrMsg
) {
440 if (buildPaths(false, ErrMsg
))
442 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
445 (std::find(Paths
.begin(), Paths
.end(), I
->getPath()) != Paths
.end())) {
447 // Make sure the intervening directories are created
449 sys::Path
dirs(I
->getPath());
450 dirs
.eraseComponent();
451 if (dirs
.createDirectoryOnDisk(/*create_parents=*/true, ErrMsg
))
455 // Open up a file stream for writing
456 std::ios::openmode io_mode
= std::ios::out
| std::ios::trunc
|
458 std::ofstream
file(I
->getPath().c_str(), io_mode
);
460 // Get the data and its length
461 const char* data
= reinterpret_cast<const char*>(I
->getData());
462 unsigned len
= I
->getSize();
465 file
.write(data
,len
);
468 // If we're supposed to retain the original modification times, etc. do so
471 I
->getPath().setStatusInfoOnDisk(I
->getFileStatus());
477 // doDelete - Implement the delete operation. This function deletes zero or more
478 // members from the archive. Note that if the count is specified, there should
479 // be no more than one path in the Paths list or else this algorithm breaks.
480 // That check is enforced in parseCommandLine (above).
482 doDelete(std::string
* ErrMsg
) {
483 if (buildPaths(false, ErrMsg
))
487 unsigned countDown
= Count
;
488 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
490 if (std::find(Paths
.begin(), Paths
.end(), I
->getPath()) != Paths
.end()) {
491 if (countDown
== 1) {
492 Archive::iterator J
= I
;
494 TheArchive
->erase(J
);
502 // We're done editting, reconstruct the archive.
503 if (TheArchive
->writeToDisk(SymTable
,TruncateNames
,Compression
,ErrMsg
))
510 // doMore - Implement the move operation. This function re-arranges just the
511 // order of the archive members so that when the archive is written the move
512 // of the members is accomplished. Note the use of the RelPos variable to
513 // determine where the items should be moved to.
515 doMove(std::string
* ErrMsg
) {
516 if (buildPaths(false, ErrMsg
))
519 // By default and convention the place to move members to is the end of the
521 Archive::iterator moveto_spot
= TheArchive
->end();
523 // However, if the relative positioning modifiers were used, we need to scan
524 // the archive to find the member in question. If we don't find it, its no
525 // crime, we just move to the end.
526 if (AddBefore
|| InsertBefore
|| AddAfter
) {
527 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
529 if (RelPos
== I
->getPath().toString()) {
541 // Keep a list of the paths remaining to be moved
542 std::set
<sys::Path
> remaining(Paths
);
544 // Scan the archive again, this time looking for the members to move to the
546 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
547 I
!= E
&& !remaining
.empty(); ++I
) {
548 std::set
<sys::Path
>::iterator found
=
549 std::find(remaining
.begin(),remaining
.end(),I
->getPath());
550 if (found
!= remaining
.end()) {
551 if (I
!= moveto_spot
)
552 TheArchive
->splice(moveto_spot
,*TheArchive
,I
);
553 remaining
.erase(found
);
557 // We're done editting, reconstruct the archive.
558 if (TheArchive
->writeToDisk(SymTable
,TruncateNames
,Compression
,ErrMsg
))
565 // doQuickAppend - Implements the 'q' operation. This function just
566 // indiscriminantly adds the members to the archive and rebuilds it.
568 doQuickAppend(std::string
* ErrMsg
) {
569 // Get the list of paths to append.
570 if (buildPaths(true, ErrMsg
))
575 // Append them quickly.
576 for (std::set
<sys::Path
>::iterator PI
= Paths
.begin(), PE
= Paths
.end();
578 if (TheArchive
->addFileBefore(*PI
,TheArchive
->end(),ErrMsg
))
582 // We're done editting, reconstruct the archive.
583 if (TheArchive
->writeToDisk(SymTable
,TruncateNames
,Compression
,ErrMsg
))
590 // doReplaceOrInsert - Implements the 'r' operation. This function will replace
591 // any existing files or insert new ones into the archive.
593 doReplaceOrInsert(std::string
* ErrMsg
) {
595 // Build the list of files to be added/replaced.
596 if (buildPaths(true, ErrMsg
))
601 // Keep track of the paths that remain to be inserted.
602 std::set
<sys::Path
> remaining(Paths
);
604 // Default the insertion spot to the end of the archive
605 Archive::iterator insert_spot
= TheArchive
->end();
607 // Iterate over the archive contents
608 for (Archive::iterator I
= TheArchive
->begin(), E
= TheArchive
->end();
609 I
!= E
&& !remaining
.empty(); ++I
) {
611 // Determine if this archive member matches one of the paths we're trying
614 std::set
<sys::Path
>::iterator found
= remaining
.end();
615 for (std::set
<sys::Path
>::iterator RI
= remaining
.begin(),
616 RE
= remaining
.end(); RI
!= RE
; ++RI
) {
617 std::string
compare(RI
->toString());
618 if (TruncateNames
&& compare
.length() > 15) {
619 const char* nm
= compare
.c_str();
620 unsigned len
= compare
.length();
621 size_t slashpos
= compare
.rfind('/');
622 if (slashpos
!= std::string::npos
) {
628 compare
.assign(nm
,len
);
630 if (compare
== I
->getPath().toString()) {
636 if (found
!= remaining
.end()) {
638 sys::PathWithStatus
PwS(*found
);
639 const sys::FileStatus
*si
= PwS
.getFileStatus(false, &Err
);
644 // Replace the item only if it is newer.
645 if (si
->modTime
> I
->getModTime())
646 if (I
->replaceWith(*found
, ErrMsg
))
649 // Replace the item regardless of time stamp
650 if (I
->replaceWith(*found
, ErrMsg
))
654 // We purposefully ignore directories.
657 // Remove it from our "to do" list
658 remaining
.erase(found
);
661 // Determine if this is the place where we should insert
662 if ((AddBefore
|| InsertBefore
) && (RelPos
== I
->getPath().toString()))
664 else if (AddAfter
&& (RelPos
== I
->getPath().toString())) {
670 // If we didn't replace all the members, some will remain and need to be
671 // inserted at the previously computed insert-spot.
672 if (!remaining
.empty()) {
673 for (std::set
<sys::Path
>::iterator PI
= remaining
.begin(),
674 PE
= remaining
.end(); PI
!= PE
; ++PI
) {
675 if (TheArchive
->addFileBefore(*PI
,insert_spot
, ErrMsg
))
680 // We're done editting, reconstruct the archive.
681 if (TheArchive
->writeToDisk(SymTable
,TruncateNames
,Compression
,ErrMsg
))
688 // main - main program for llvm-ar .. see comments in the code
689 int main(int argc
, char **argv
) {
690 // Print a stack trace if we signal out.
691 sys::PrintStackTraceOnErrorSignal();
692 PrettyStackTraceProgram
X(argc
, argv
);
693 llvm_shutdown_obj Y
; // Call llvm_shutdown() on exit.
695 // Have the command line options parsed and handle things
696 // like --help and --version.
697 cl::ParseCommandLineOptions(argc
, argv
,
698 "LLVM Archiver (llvm-ar)\n\n"
699 " This program archives bitcode files into single libraries\n"
704 // Make sure we don't exit with "unhandled exception".
706 // Do our own parsing of the command line because the CommandLine utility
707 // can't handle the grouped positional parameters without a dash.
708 ArchiveOperation Operation
= parseCommandLine();
710 // Check the path name of the archive
711 sys::Path ArchivePath
;
712 if (!ArchivePath
.set(ArchiveName
))
713 throw std::string("Archive name invalid: ") + ArchiveName
;
715 // Create or open the archive object.
716 if (!ArchivePath
.exists()) {
717 // Produce a warning if we should and we're creating the archive
719 std::cerr
<< argv
[0] << ": creating " << ArchivePath
.toString() << "\n";
720 TheArchive
= Archive::CreateEmpty(ArchivePath
);
721 TheArchive
->writeToDisk();
724 TheArchive
= Archive::OpenAndLoad(ArchivePath
, &Error
);
725 if (TheArchive
== 0) {
726 std::cerr
<< argv
[0] << ": error loading '" << ArchivePath
<< "': "
732 // Make sure we're not fooling ourselves.
733 assert(TheArchive
&& "Unable to instantiate the archive");
735 // Make sure we clean up the archive even on failure.
736 std::auto_ptr
<Archive
> AutoArchive(TheArchive
);
738 // Perform the operation
740 bool haveError
= false;
742 case Print
: haveError
= doPrint(&ErrMsg
); break;
743 case Delete
: haveError
= doDelete(&ErrMsg
); break;
744 case Move
: haveError
= doMove(&ErrMsg
); break;
745 case QuickAppend
: haveError
= doQuickAppend(&ErrMsg
); break;
746 case ReplaceOrInsert
: haveError
= doReplaceOrInsert(&ErrMsg
); break;
747 case DisplayTable
: haveError
= doDisplayTable(&ErrMsg
); break;
748 case Extract
: haveError
= doExtract(&ErrMsg
); break;
750 std::cerr
<< argv
[0] << ": No operation was selected.\n";
754 std::cerr
<< argv
[0] << ": " << ErrMsg
<< "\n";
757 } catch (const char*msg
) {
758 // These errors are usage errors, thrown only by the various checks in the
760 std::cerr
<< argv
[0] << ": " << msg
<< "\n\n";
761 cl::PrintHelpMessage();
763 } catch (const std::string
& msg
) {
764 // These errors are thrown by LLVM libraries (e.g. lib System) and represent
765 // a more serious error so we bump the exitCode and don't print the usage.
766 std::cerr
<< argv
[0] << ": " << msg
<< "\n";
769 // This really shouldn't happen, but just in case ....
770 std::cerr
<< argv
[0] << ": An unexpected unknown exception occurred.\n";
774 // Return result code back to operating system.