/*
* Copyright (C) 2010 Vyatta, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cnode;
////// constants
static const string PFX_DIFF_ADD = "+"; // added
static const string PFX_DIFF_DEL = "-"; // deleted
static const string PFX_DIFF_UPD = ">"; // changed
static const string PFX_DIFF_NONE = " ";
static const string PFX_DIFF_NULL = "";
static const string PFX_DEACT_D = "!"; // deactivated
static const string PFX_DEACT_DP = "D"; // deactivate pending
static const string PFX_DEACT_AP = "A"; // activate pending
static const string PFX_DEACT_NONE = " ";
////// static (internal) functions
static void
_show_diff(CfgNode *cfg1, CfgNode *cfg2, int level, bool show_def,
bool hide_secret);
static void
_print_value_str(const string& name, const char *vstr, bool hide_secret)
{
// handle secret hiding first
if (hide_secret) {
static const char *sname[] = { "passphrase", "password",
"pre-shared-secret", "key", NULL };
static size_t slen[] = { 10, 8, 17, 3, 0 };
size_t nlen = name.length();
for (size_t i = 0; sname[i]; i++) {
if (nlen < slen[i]) {
// can't match
continue;
}
if (name.find(sname[i], nlen - slen[i]) != name.npos) {
// found secret
printf("****************");
return;
}
}
}
const char *quote = "";
size_t vlen = strlen(vstr);
if (*vstr == 0 || strcspn(vstr, "*}{;\011\012\013\014\015 ") < vlen) {
quote = "\"";
}
printf("%s%s%s", quote, vstr, quote);
}
static void
_diff_print_indent(CfgNode *cfg1, CfgNode *cfg2, int level,
const char *pfx_diff)
{
const char *pfx_deact = PFX_DEACT_NONE.c_str();
bool de1 = (cfg1 ? cfg1->isDeactivated() : false);
bool de2 = (cfg2 ? cfg2->isDeactivated() : false);
if (de1) {
if (de2) {
pfx_deact = PFX_DEACT_D.c_str();
} else {
pfx_deact = PFX_DEACT_AP.c_str();
}
} else {
if (de2) {
pfx_deact = PFX_DEACT_DP.c_str();
}
// 4th case handled by default
}
printf("%s %s", pfx_deact, pfx_diff);
for (int i = 0; i < level; i++) {
printf(" ");
}
}
static void
_diff_print_comment(CfgNode *cfg1, CfgNode *cfg2, int level)
{
const char *pfx_diff = PFX_DIFF_NONE.c_str();
string comment = "";
if (cfg1 != cfg2) {
string c1 = (cfg1 ? cfg1->getComment() : "");
string c2 = (cfg2 ? cfg2->getComment() : "");
if (c1 != "") {
if (c2 != "") {
// in both
comment = c2;
if (c1 != c2) {
pfx_diff = PFX_DIFF_UPD.c_str();
}
} else {
// only in cfg1
comment = c1;
pfx_diff = PFX_DIFF_DEL.c_str();
}
} else {
if (c2 != "") {
// only in cfg2
comment = c2;
pfx_diff = PFX_DIFF_ADD.c_str();
}
// 4th case handled by default
}
} else {
// same node => no diff
pfx_diff = PFX_DIFF_NULL.c_str();
comment = cfg1->getComment();
}
if (comment == "") {
// no comment
return;
}
_diff_print_indent(cfg1, cfg2, level, pfx_diff);
printf("/* %s */\n", comment.c_str());
}
static bool
_diff_check_and_show_leaf(CfgNode *cfg1, CfgNode *cfg2, int level,
bool show_def, bool hide_secret)
{
if ((cfg1 && !cfg1->isLeaf()) || (cfg2 && !cfg2->isLeaf())) {
// not a leaf node
return false;
}
CfgNode *cfg = NULL;
const char *force_pfx_diff = NULL;
if (!cfg1) {
cfg = cfg2;
force_pfx_diff = PFX_DIFF_ADD.c_str();
} else {
cfg = cfg1;
if (!cfg2) {
force_pfx_diff = PFX_DIFF_DEL.c_str();
} else if (cfg1 == cfg2) {
force_pfx_diff = PFX_DIFF_NULL.c_str();
}
}
_diff_print_comment(cfg1, cfg2, level);
if (cfg->isMulti()) {
// multi-value node
if (force_pfx_diff) {
// simple case: just use the same diff prefix for all values
const vector& vvec = cfg->getValues();
for (size_t i = 0; i < vvec.size(); i++) {
_diff_print_indent(cfg1, cfg2, level, force_pfx_diff);
printf("%s ", cfg->getName().c_str());
_print_value_str(cfg->getName(), vvec[i].c_str(), hide_secret);
printf("\n");
}
} else {
// need to actually do a diff.
// this follows the original perl logic.
const vector& ovec = cfg1->getValues();
const vector& nvec = cfg2->getValues();
vector values;
vector pfxs;
Cstore::MapT nmap;
for (size_t i = 0; i < nvec.size(); i++) {
nmap[nvec[i]] = true;
}
Cstore::MapT omap;
for (size_t i = 0; i < ovec.size(); i++) {
omap[ovec[i]] = true;
if (nmap.find(ovec[i]) == nmap.end()) {
values.push_back(ovec[i]);
pfxs.push_back(PFX_DIFF_DEL.c_str());
}
}
for (size_t i = 0; i < nvec.size(); i++) {
values.push_back(nvec[i]);
if (omap.find(nvec[i]) == omap.end()) {
pfxs.push_back(PFX_DIFF_ADD.c_str());
} else if (i < ovec.size() && nvec[i] == ovec[i]) {
pfxs.push_back(PFX_DIFF_NONE.c_str());
} else {
pfxs.push_back(PFX_DIFF_UPD.c_str());
}
}
for (size_t i = 0; i < values.size(); i++) {
_diff_print_indent(cfg1, cfg2, level, pfxs[i]);
printf("%s ", cfg->getName().c_str());
_print_value_str(cfg->getName(), values[i].c_str(), hide_secret);
printf("\n");
}
}
} else {
// single-value node
if (show_def || !cfg->isDefault()) {
string val = cfg->getValue();
if (!force_pfx_diff) {
const string& val1 = cfg1->getValue();
val = cfg2->getValue();
if (val == val1) {
force_pfx_diff = PFX_DIFF_NONE.c_str();
} else {
force_pfx_diff = PFX_DIFF_UPD.c_str();
}
}
_diff_print_indent(cfg1, cfg2, level, force_pfx_diff);
printf("%s ", cfg->getName().c_str());
_print_value_str(cfg->getName(), val.c_str(), hide_secret);
printf("\n");
}
}
return true;
}
static void
_diff_show_other(CfgNode *cfg1, CfgNode *cfg2, int level, bool show_def,
bool hide_secret)
{
CfgNode *cfg = NULL;
const char *pfx_diff = PFX_DIFF_NONE.c_str();
if (!cfg1) {
cfg = cfg2;
pfx_diff = PFX_DIFF_ADD.c_str();
} else {
cfg = cfg1;
if (!cfg2) {
pfx_diff = PFX_DIFF_DEL.c_str();
} else if (cfg1 == cfg2) {
pfx_diff = PFX_DIFF_NULL.c_str();
}
}
/* only print "this" node if it
* (1) is a tag value or an intermediate node,
* (2) is not "root", and
* (3) has a "name".
*/
const string& name = cfg->getName();
bool print_this = (((cfg1 && (!cfg1->isTag() || cfg1->isValue()))
|| (cfg2 && (!cfg2->isTag() || cfg2->isValue())))
&& level >= 0 && name.size() > 0);
if (print_this) {
_diff_print_comment(cfg1, cfg2, level);
_diff_print_indent(cfg1, cfg2, level, pfx_diff);
if (cfg->isValue()) {
// at tag value
printf("%s %s", name.c_str(), cfg->getValue().c_str());
} else {
// at intermediate node
printf("%s", name.c_str());
}
printf("%s\n", (cfg->isLeafTypeless() ? "" : " {"));
}
// handle child nodes
vector cnodes1, cnodes2;
if (cfg1) {
cnodes1 = cfg1->getChildNodes();
}
if (cfg2) {
cnodes2 = cfg2->getChildNodes();
}
Cstore::MapT map;
Cstore::MapT nmap1, nmap2;
for (size_t i = 0; i < cnodes1.size(); i++) {
string key
= ((cfg->isTag() && !cfg->isValue())
? cnodes1[i]->getValue() : cnodes1[i]->getName());
map[key] = true;
nmap1[key] = cnodes1[i];
}
for (size_t i = 0; i < cnodes2.size(); i++) {
string key
= ((cfg->isTag() && !cfg->isValue())
? cnodes2[i]->getValue() : cnodes2[i]->getName());
map[key] = true;
nmap2[key] = cnodes2[i];
}
vector cnodes;
Cstore::MapT::iterator it = map.begin();
for (; it != map.end(); ++it) {
cnodes.push_back((*it).first);
}
Cstore::sortNodes(cnodes);
for (size_t i = 0; i < cnodes.size(); i++) {
bool in1 = (nmap1.find(cnodes[i]) != nmap1.end());
bool in2 = (nmap2.find(cnodes[i]) != nmap2.end());
CfgNode *c1 = (in1 ? nmap1[cnodes[i]] : NULL);
CfgNode *c2 = (in2 ? nmap2[cnodes[i]] : NULL);
int next_level = level + 1;
if (!print_this) {
next_level = (level >= 0 ? level : 0);
}
_show_diff(c1, c2, next_level, show_def, hide_secret);
}
// finish printing "this" node if necessary
if (print_this && !cfg->isLeafTypeless()) {
_diff_print_indent(cfg1, cfg2, level, pfx_diff);
printf("}\n");
}
}
static void
_show_diff(CfgNode *cfg1, CfgNode *cfg2, int level, bool show_def,
bool hide_secret)
{
// if doesn't exist, treat as NULL
if (cfg1 && !cfg1->exists()) {
cfg1 = NULL;
}
if (cfg2 && !cfg2->exists()) {
cfg2 = NULL;
}
/* cfg1 and cfg2 point to the same config node in two configs. a "diff"
* output is shown comparing the two configs recursively with this node
* as the root of the config tree.
*
* there are four possible scenarios:
* (1) (cfg1 && cfg2) && (cfg1 != cfg2): node exists in both config.
* (2) (cfg1 && cfg2) && (cfg1 == cfg2): the two point to the same config.
* this will be just a "show".
* (3) (!cfg1 && cfg2): node exists in cfg2 but not in cfg1 (added).
* (4) (cfg1 && !cfg2): node exists in cfg1 but not in cfg1 (deleted).
*
* calling this with both NULL is invalid.
*/
if (!cfg1 && !cfg2) {
fprintf(stderr, "_show_diff error (both config NULL)\n");
exit(1);
}
if (_diff_check_and_show_leaf(cfg1, cfg2, (level >= 0 ? level : 0),
show_def, hide_secret)) {
// leaf node has been shown. done.
return;
} else {
// intermediate node, tag node, or tag value
_diff_show_other(cfg1, cfg2, level, show_def, hide_secret);
}
}
////// algorithms
void
cnode::show_diff(const CfgNode& cfg1, const CfgNode& cfg2, bool show_def,
bool hide_secret)
{
if (cfg1.isInvalid() || cfg2.isInvalid()) {
printf("Specified configuration path is not valid\n");
return;
}
if ((cfg1.isEmpty() && cfg2.isEmpty())
|| (!cfg1.exists() && !cfg2.exists())) {
printf("Configuration under specified path is empty\n");
return;
}
_show_diff(const_cast(&cfg1), const_cast(&cfg2), -1,
show_def, hide_secret);
}