blob: 4889c74f559ad59488b2182119062edba6baf1fb [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2013-2020 Red Hat, Inc.
#include <sys/types.h>
#include <sys/stat.h>// The two above is for 'stat'
#include <cstring> // for 'strcmp'
#include <cstdlib> // for 'system'
#include <iostream>
#include <fstream>
#include <list>
#include "abg-corpus.h"
#include "abg-reader.h"
#include "abg-writer.h"
#include "abg-tools-utils.h"
using std::cerr;
using std::cout;
using std::ostream;
using std::string;
using std::list;
struct options
{
bool list_content;
string extract_dest;
string archive;
list<string> in_files;
string out_dir;
options()
: list_content(false)
{
}
};//end options
/// Display a message explaining the usage of the program on stdout.
static void
display_usage(const string &prog_name, ostream& out)
{
out << "usage: " << prog_name << " [options] [archive-file-path]\n"
<< " where options are: \n"
<< "--help|-h display this usage message\n"
<< "--list|l <archive> list the archive content\n"
<< "--add|-a <files-to-add> <archive> add files to an archive\n"
<< "--extract|x [dest-dir] <archive> extract archive content\n"
;
}
/// Parse the command line arguments and populate an instance of \a
/// options as a result.
///
/// @param argc the number of words on the command line, including the
/// program name.
///
/// @param argv an array of words representing the command line. This
/// is the thing that is parsed.
///
/// @param opts the options that are set as a result of parsing the
/// command line. This output parameter is set iff the function
/// returs true.
///
/// @return true if the function could make sense of the command line
/// and parsed it correctly. Otherwise, return false. If this
/// function returns false, the caller would be well inspired to call
/// display_usage() to remind the user how to properly invoke this
/// program so that its command line makes sense.
static bool
parse_args(int argc, char* argv[], options& opts)
{
if (argc < 2)
return false;
for (int i = 1; i < argc; ++i)
{
char* arg = argv[i];
if (! strcmp(arg, "--help")
||! strcmp(arg, "-h"))
return false;
if (arg[0] != '-')
opts.archive = arg;
else if (! strcmp(arg, "--list")
||! strcmp(arg, "-l"))
opts.list_content = true;
else if (! strcmp(arg, "--add")
|| ! strcmp(arg,"-a"))
{
int arg_index0, arg_index1;
char *f = 0;
for (arg_index0 = i + 1; arg_index0 < argc; ++arg_index0)
{
// --add must be followed by at N words that don't start
// by '-' (N > 1). The first N-1 words are the
// arguments of --add (the files to add to the archive)
// and the last one is the name of the archive to add
// the files to.
f = argv[arg_index0];
if (f[0] == '-')
break;
arg_index1 = arg_index0 + 1;
if (arg_index1 >= argc
|| argv[arg_index1][0] == '-')
break;
opts.in_files.push_back(f);
}
if (opts.in_files.empty())
return false;
}
else if (! strcmp(arg, "--extract")
|| ! strcmp(arg, "-x"))
{
int arg_index = i + 1, arch_index = arg_index + 1;
if (arg_index < argc
&& argv[arg_index][0] != '-'
&& arch_index < argc
&& argv[arch_index][0] != '-')
opts.extract_dest = argv[arg_index];
else if (arg_index < argc
&& argv[arg_index][0] != '-')
// No destination directory argument was given for the
// --extract option, so consider it to be the current
// directory
opts.extract_dest = ".";
else
return false;
}
}
return true;
}
using abigail::corpus;
using abigail::corpus_sptr;
using abigail::translation_unit;
using abigail::translation_unit_sptr;
using abigail::translation_units;
using abigail::xml_reader::read_corpus_from_file;
using abigail::xml_writer::write_corpus_to_archive;
/// List the content of a given archive. The names of the files of
/// the archive are then displayed on stdout.
///
/// @param archive_path a path to the file containing the archive file
/// to list the content of.
///
/// @return true upon successful completion, false otherwise.
static bool
list_content(const string& archive_path)
{
if (archive_path.empty())
{
cerr << "Empty archive path\n";
return false;
}
corpus_sptr archive = read_corpus_from_file(archive_path);
if (!archive)
{
cerr << "Could not read archive at '" << archive_path << "'\n";
return false;
}
for (translation_units::const_iterator i =
archive->get_translation_units().begin();
i != archive->get_translation_units().end();
++i)
cout << (*i)->get_path() << "\n";
return true;
}
/// Add a translation unit to an archive -- or create one for that
/// matter.
///
/// @param tu_paths a list of paths to add to the archive.
///
/// @param archive_path the path of the archive to either open or
/// create. The specified in \tu_paths are then added to this
/// archive. Note that this function creates the entire directory
/// tree needed up to \a archive_path, if needed.
///
/// @return true upon successful completion, false otherwise.
static bool
add_tus_to_archive(const list<string>& tu_paths,
const string& archive_path)
{
translation_units tus;
corpus corp(archive_path);
bool added_some_tus = false;
for (list<string>::const_iterator i = tu_paths.begin();
i != tu_paths.end();
++i)
{
translation_unit_sptr tu =
abigail::xml_reader::read_translation_unit_from_file(*i);
if (!tu)
{
cerr << "could not read binary instrumentation file '"
<< *i
<< "'\n";
continue;
}
corp.add(tu);
added_some_tus = true;
}
if (added_some_tus)
{
if (!write_corpus_to_archive(corp))
{
cerr << "could not write archive file '"
<< corp.get_path()
<< "'\n";
return false;
}
}
return true;
}
/// Extract translation units from a given archive.
///
/// @param dest_path the path of the destination directory which the
/// elements of the archive are to be extracted under.
///
/// @param archive_path the path to the archive to extract. The
/// archive must exist and be accessible with read privileges.
///
/// @return true upon successful completion, false otherwise.
static bool
extract_tus_from_archive(const string& dest_path,
const string& archive_path)
{
if (dest_path.empty())
{
cerr << "empty file directory\n";
return false;
}
corpus_sptr archive(new corpus(archive_path));
if (read_corpus_from_file(archive) < 1)
{
cerr << "could not read archive at '"
<< archive_path
<< "'\n;";
return false;
}
string cmd = "mkdir -p " + dest_path;
if (system(cmd.c_str()))
{
cerr << "could not create file directory '"
<< dest_path
<< "'\n";
return false;
}
for (translation_units::const_iterator i =
archive->get_translation_units().begin();
i != archive->get_translation_units().end();
++i)
{
string dest = dest_path + "/" + (*i)->get_path();
if (!abigail::tools_utils::ensure_parent_dir_created(dest))
{
cerr << "could not create parent director for '" << dest << "'\n";
return false;
}
if (!abigail::xml_writer::write_translation_unit(**i, /*indent=*/0, dest))
{
cerr << "could not write binary instrumentation file to '"
<< dest
<< "'\n";
return false;
}
}
return true;
}
/// Parse the command line and perform the archive-related operations
/// asked by the user, if the command line makes sense; otherwise,
/// display a usage help message and bail out.
int
main(int argc, char* argv[])
{
options opts;
if (!parse_args(argc, argv, opts)
|| opts.archive.empty())
{
display_usage(argv[0], cout);
return -1;
}
if (opts.list_content)
return !list_content(opts.archive);
else if (!opts.in_files.empty())
return !add_tus_to_archive(opts.in_files, opts.archive);
else if (!opts.extract_dest.empty())
return !extract_tus_from_archive(opts.extract_dest, opts.archive);
else
{
display_usage(argv[0], cout);
return -1;
}
return 0;
}