+-------------------------------------------------------------------
+Wed Nov 18 12:13:56 CET 2020 - aschnell@suse.com
+
+- use C++11 regexes instead of own regcomp/regexec wrapper class
+ (see gh#openSUSE/snapper#583)
+
-------------------------------------------------------------------
Tue Sep 22 09:17:28 CEST 2020 - aschnell@suse.com
/*
* Copyright (c) [2004-2015] Novell, Inc.
+ * Copyright (c) 2020 SUSE LLC
*
* All Rights Reserved.
*
#include <unistd.h>
#include <fstream>
+#include <regex>
#include "snapper/Log.h"
#include "snapper/AppUtil.h"
AsciiFileReader::AsciiFileReader(int fd)
- : file(fdopen(fd, "r")), buffer(NULL), len(0)
+ : file(fdopen(fd, "r"))
{
- if (file == NULL)
+ if (!file)
{
y2war("file is NULL");
SN_THROW(FileNotFoundException());
AsciiFileReader::AsciiFileReader(FILE* file)
- : file(file), buffer(NULL), len(0)
+ : file(file)
{
- if (file == NULL)
+ if (!file)
{
y2war("file is NULL");
SN_THROW(FileNotFoundException());
AsciiFileReader::AsciiFileReader(const string& filename)
- : file(NULL), buffer(NULL), len(0)
+ : file(fopen(filename.c_str(), "re"))
{
- file = fopen(filename.c_str(), "re");
- if (file == NULL)
+ if (!file)
{
y2war("open for '" << filename << "' failed");
SN_THROW(FileNotFoundException());
void
SysconfigFile::checkKey(const string& key) const
{
- Regex rx("^" "([0-9A-Z_]+)" "$");
+ static const regex rx("([0-9A-Z_]+)", regex::extended);
- if (!rx.match(key))
+ if (!regex_match(key, rx))
SN_THROW(InvalidKeyException());
}
{
checkKey(key);
- Regex rx('^' + Regex::ws +
- key + '=' + "(['\"]?)([^'\"]*)\\1" +
- '(' + Regex::ws + Regex::trailing_comment + ")$");
+ modified = true;
- vector<string>::iterator it = find_if(lines(), regex_matches(rx));
- if (it == lines().end())
- {
- string line = key + "=\"" + value + "\"";
- push_back(line);
- }
- else
+ for (vector<string>::iterator it = Lines_C.begin(); it != Lines_C.end(); ++it)
{
- string line = key + "=\"" + value + "\"" + rx.cap(3);
- *it = line;
+ ParsedLine parsed_line;
+
+ if (parse_line(*it, parsed_line) && parsed_line.key == key)
+ {
+ string line = key + "=\"" + value + "\"" + parsed_line.comment;
+ *it = line;
+ return;
+ }
}
- modified = true;
+ string line = key + "=\"" + value + "\"";
+ push_back(line);
}
bool
SysconfigFile::getValue(const string& key, string& value) const
{
- Regex rx('^' + Regex::ws +
- key + '=' + "(['\"]?)([^'\"]*)\\1" +
- Regex::ws + Regex::trailing_comment + '$');
+ for (const string& line : Lines_C)
+ {
+ ParsedLine parsed_line;
- if (find_if(lines(), regex_matches(rx)) == lines().end())
- return false;
+ if (parse_line(line, parsed_line) && parsed_line.key == key)
+ {
+ value = parsed_line.value;
+ y2mil("key:" << key << " value:" << value);
+ return true;
+ }
+ }
- value = rx.cap(2);
- y2mil("key:" << key << " value:" << value);
- return true;
+ return false;
}
{
map<string, string> ret;
- Regex rx('^' + Regex::ws +
- "([0-9A-Z_]+)" + '=' + "(['\"]?)([^'\"]*)\\2" +
- Regex::ws + Regex::trailing_comment + '$');
-
- for (vector<string>::const_iterator it = Lines_C.begin(); it != Lines_C.end(); ++it)
+ for (const string& line : Lines_C)
{
- if (rx.match(*it))
- ret[rx.cap(1)] = rx.cap(3);
+ ParsedLine parsed_line;
+
+ if (parse_line(line, parsed_line))
+ ret[parsed_line.key] = parsed_line.value;
}
return ret;
}
+
+ bool
+ SysconfigFile::parse_line(const string& line, ParsedLine& parsed_line) const
+ {
+ const string whitespace = "[ \t]*";
+ const string comment = "(#.*)?";
+
+ // Note: Avoid back references. Whether they work depend on the regex flags. Also
+ // the old regcomp/regexec based implementation did not work with them with some
+ // libc implementations.
+
+ static const regex rx1(whitespace + "([0-9A-Z_]+)" + '=' + "\"([^\"]*)\"" +
+ '(' + whitespace + comment + ")");
+
+ static const regex rx2(whitespace + "([0-9A-Z_]+)" + '=' + "'([^']*)'" +
+ '(' + whitespace + comment + ")");
+
+ static const regex rx3(whitespace + "([0-9A-Z_]+)" + '=' + "([^ \t]*)" +
+ '(' + whitespace + comment + ")");
+
+ smatch match;
+
+ if (!regex_match(line, match, rx1) && !regex_match(line, match, rx2) && !regex_match(line, match, rx3))
+ return false;
+
+ parsed_line.key = match[1];
+ parsed_line.value = match[2];
+ parsed_line.comment = match[3];
+
+ return true;
+ }
+
}
/*
* Copyright (c) [2004-2015] Novell, Inc.
+ * Copyright (c) 2020 SUSE LLC
*
* All Rights Reserved.
*
private:
- FILE* file;
- char* buffer;
- size_t len;
+ FILE* file = nullptr;
+ char* buffer = nullptr;
+ size_t len = 0;
};
bool modified;
+ struct ParsedLine
+ {
+ string key;
+ string value;
+ string comment;
+ };
+
+ bool parse_line(const string& line, ParsedLine& parsed_line) const;
+
};
}
/*
* Copyright (c) [2011-2015] Novell, Inc.
- * Copyright (c) [2016-2018] SUSE LLC
+ * Copyright (c) [2016-2020] SUSE LLC
*
* All Rights Reserved.
*
#include <boost/version.hpp>
#include <boost/thread.hpp>
#endif
+#include <regex>
#include <boost/algorithm/string.hpp>
#include "snapper/Log.h"
#include "snapper/SnapperDefines.h"
#include "snapper/Acls.h"
#include "snapper/Exception.h"
-#include "snapper/Regex.h"
#ifdef ENABLE_ROLLBACK
#include "snapper/MntTable.h"
#endif
{
string path = get_subvolume(fd, id);
- Regex rx("/([0-9]+)/snapshot$");
- if (!rx.match(path))
+ static const regex rx("/([0-9]+)/snapshot$", regex::extended);
+ smatch match;
+
+ if (!regex_search(path, match, rx))
return make_pair(false, 0);
- unsigned int num = stoi(rx.cap(1));
+ unsigned int num = stoi(match[1].str());
if (!checkSnapshot(num))
return make_pair(false, 0);
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/types.h>
+#include <regex>
#include <boost/algorithm/string.hpp>
#include "snapper/Log.h"
#include "snapper/SnapperTmpl.h"
#include "snapper/SystemCmd.h"
#include "snapper/SnapperDefines.h"
-#include "snapper/Regex.h"
#include "snapper/LvmCache.h"
#ifdef ENABLE_SELINUX
#include "snapper/Selinux.h"
Filesystem*
Lvm::create(const string& fstype, const string& subvolume, const string& root_prefix)
{
- Regex rx("^lvm\\(([_a-z0-9]+)\\)$");
- if (rx.match(fstype))
- return new Lvm(subvolume, root_prefix, rx.cap(1));
+ static const regex rx("lvm\\(([_a-z0-9]+)\\)", regex::extended);
+ smatch match;
+
+ if (regex_match(fstype, match, rx))
+ return new Lvm(subvolume, root_prefix, match[1]);
return NULL;
}
string
Lvm::getDevice(unsigned int num) const
{
- return "/dev/mapper/" + boost::replace_all_copy(vg_name, "-", "--") + "-" +
+ return DEV_MAPPER_DIR "/" + boost::replace_all_copy(vg_name, "-", "--") + "-" +
boost::replace_all_copy(snapshotLvName(num), "-", "--");
}
}
else
{
- Regex rx(".*LVM[[:space:]]+version:[[:space:]]+([0-9]+)\\.([0-9]+)\\.([0-9]+).*$");
+ static const regex rx(".*LVM[[:space:]]+version:[[:space:]]+([0-9]+)\\.([0-9]+)\\.([0-9]+).*$",
+ regex::extended);
+ smatch match;
- if (!rx.match(cmd.get_stdout().front()))
+ if (!regex_search(cmd.get_stdout().front(), match, rx))
{
y2war("LVM version format didn't match");
}
{
uint16_t maj, min, rev;
- rx.cap(1) >> maj;
- rx.cap(2) >> min;
- rx.cap(3) >> rev;
+ match[1] >> maj;
+ match[2] >> min;
+ match[3] >> rev;
lvm_version version(maj, min, rev);
// empty or " -K" if lvm supports ignore activation skip flag
string ignoreactivationskip;
// true if lvm2 supports time info stored in metadata
- bool time_support;
+ bool time_support = false;
};
class SelinuxLabelHandle;
*/
+#include <regex>
#include <boost/algorithm/string.hpp>
#include "snapper/LvmUtils.h"
-#include "snapper/Regex.h"
-#include "snapper/AppUtil.h"
+#include "snapper/SnapperDefines.h"
namespace snapper
pair<string, string>
split_device_name(const string& name)
{
- Regex rx("^/dev/mapper/(.*[^-])-([^-].*)$");
- if (!rx.match(name))
+ static const std::regex rx(DEV_MAPPER_DIR "/(.*[^-])-([^-].*)", std::regex::extended);
+ std::smatch match;
+
+ if (!regex_match(name, match, rx))
throw std::runtime_error("faild to split device name into volume group and "
"logical volume name");
- string vg_name = boost::replace_all_copy(rx.cap(1), "--", "-");
- string lv_name = boost::replace_all_copy(rx.cap(2), "--", "-");
+ string vg_name = boost::replace_all_copy(match[1].str(), "--", "-");
+ string lv_name = boost::replace_all_copy(match[2].str(), "--", "-");
return make_pair(vg_name, lv_name);
}
}
+
}
* find current contact information at www.novell.com.
*/
+
+// This file is obsolete. It is only present for compatibility of libsnapper, esp. the
+// Regex class was used in older versions of the zypp-plugin and we must avoid potentially
+// problems during updates.
+
+
#include <stdexcept>
#include "snapper/Regex.h"
*/
+// This file is obsolete. It is only present for compatibility of libsnapper, esp. the
+// Regex class was used in older versions of the zypp-plugin and we must avoid potentially
+// problems during updates.
+
+
#ifndef SNAPPER_REGEX_H
#define SNAPPER_REGEX_H
#include <sys/acl.h>
#include <acl/libacl.h>
#include <set>
+#include <regex>
#include <boost/algorithm/string.hpp>
#include "snapper/Snapper.h"
#include "snapper/BtrfsUtils.h"
#ifdef ENABLE_SELINUX
#include "snapper/Selinux.h"
-#include "snapper/Regex.h"
#endif
Snapper::syncSelinuxContextsInInfosDir(bool skip_snapshot_dir) const
{
#ifdef ENABLE_SELINUX
- Regex rx("^[0-9]+$");
- Regex rx_filelist("^filelist-[0-9]+.txt$");
+ static const regex rx("[0-9]+", regex::extended);
+ static const regex rx_filelist("filelist-[0-9]+.txt", regex::extended);
y2deb("Syncing Selinux contexts in infos dir");
vector<string> infos = infos_dir.entries();
for (vector<string>::const_iterator it1 = infos.begin(); it1 != infos.end(); ++it1)
{
- if (!rx.match(*it1))
+ if (!regex_match(*it1, rx))
continue;
SDir info_dir(infos_dir, *it1);
vector<string> info_content = info_dir.entries();
for (vector<string>::const_iterator it2 = info_content.begin(); it2 != info_content.end(); ++it2)
{
- if (!rx_filelist.match(*it2))
+ if (!regex_match(*it2, rx_filelist))
continue;
SFile fl(info_dir, *it2);
/*
* Copyright (c) [2004-2014] Novell, Inc.
+ * Copyright (c) 2020 SUSE LLC
*
* All Rights Reserved.
*
#define FILTERS_DIR "/etc/snapper/filters"
+#define DEV_DIR "/dev"
+#define DEV_MAPPER_DIR "/dev/mapper"
+
// keys from the config files
#include <ostream>
#include <boost/algorithm/string.hpp>
-#include "snapper/Regex.h"
-
namespace snapper
{
using std::vector;
- struct regex_matches
- {
- regex_matches(const Regex& t) : val(t) {}
- bool operator()(const string& s) const { return val.match(s); }
- const Regex& val;
- };
-
struct string_starts_with
{
string_starts_with(const string& t) : val(t) {}
#include <dirent.h>
#include <errno.h>
#include <string.h>
+#include <regex>
#include <boost/algorithm/string.hpp>
#include "snapper/Snapshot.h"
#include "snapper/SnapperTmpl.h"
#include "snapper/SnapperDefines.h"
#include "snapper/Exception.h"
-#include "snapper/Regex.h"
#include "snapper/Hooks.h"
void
Snapshots::read()
{
- Regex rx("^[0-9]+$");
+ static const regex rx("[0-9]+", regex::extended);
SDir infos_dir = snapper->openInfosDir();
vector<string> infos = infos_dir.entries();
for (vector<string>::const_iterator it1 = infos.begin(); it1 != infos.end(); ++it1)
{
- if (!rx.match(*it1))
+ if (!regex_match(*it1, rx))
continue;
try
BOOST_CHECK(s.getValue("S1", tmp_string));
BOOST_CHECK_EQUAL(tmp_string, "hello");
+ BOOST_CHECK(s.getValue("S2", tmp_string));
+ BOOST_CHECK_EQUAL(tmp_string, "hello");
+
bool tmp_bool;
BOOST_CHECK(s.getValue("B1", tmp_bool));
S1="hello"
+S2=hello # this is a comment
B1="yes"
B2="no"
../dbus/libdbus.la \
$(JSONC_LIBS)
-check_PROGRAMS = regex.test forwarding-zypp-plugin
+check_PROGRAMS = solvable_matcher.test forwarding-zypp-plugin
forwarding_zypp_plugin_SOURCES = \
forwarding_zypp_plugin.cc \
-lboost_system \
-lpthread
-TESTS = regex.test
+TESTS = solvable_matcher.test
-regex_test_SOURCES = regex_test.cc \
+solvable_matcher_test_SOURCES = solvable_matcher_test.cc \
solvable_matcher.cc solvable_matcher.h
-regex_test_LDADD = \
+solvable_matcher_test_LDADD = \
../snapper/libsnapper.la \
$(XML2_LIBS) \
-lboost_unit_test_framework
+++ /dev/null
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_MODULE regex
-
-#include <boost/test/unit_test.hpp>
-#include "solvable_matcher.h"
-
-static
-SolvableMatcher rx(const char* regex) {
- bool is_important = false;
- return SolvableMatcher(regex, SolvableMatcher::Kind::REGEX, is_important);
-}
-
-BOOST_AUTO_TEST_CASE(regex)
-{
- BOOST_CHECK(rx("mypkg").match("mypkg"));
- BOOST_CHECK(!rx("mypkg").match("yourpkg"));
-
- // substrings don't count
- BOOST_CHECK(!rx("foo").match("afool"));
- // prefixes must match
- BOOST_CHECK(rx("foo").match("fool"));
- // explicit anchor is OK
- BOOST_CHECK(rx("^foo").match("foo"));
- // double anchor is also OK
- BOOST_CHECK(rx("^^foo").match("foo"));
- // exclude prefix matches
- BOOST_CHECK(!rx("foo$").match("foo-devel"));
-
- SolvableMatcher ror = rx("ruby[.0-9]+-rubygem-active(model|record)");
- BOOST_CHECK(ror.match("ruby2.5-rubygem-activemodel-5.2"));
- BOOST_CHECK(ror.match("ruby2.5-rubygem-activerecord-5_1"));
-}
/*
- * Copyright (c) 2019 SUSE LLC
+ * Copyright (c) [2019-2020] SUSE LLC
*
* All Rights Reserved.
*
#include <iostream>
#include <map>
#include <string>
+#include <regex>
using namespace std;
#include <json.h>
#endif
#include "dbus/DBusConnection.h"
-#include "snapper/Regex.h"
#include "snapper/Exception.h"
using snapper::Exception;
using snapper::CodeLocation;
const string& userdata_s = it->second;
vector<string> key_values;
boost::split(key_values, userdata_s, boost::is_any_of(","));
- for (auto kv: key_values) {
- static const snapper::Regex rx_keyval("^([^=]*)=(.+)$");
- if (rx_keyval.match(kv)) {
- string key = boost::trim_copy(rx_keyval.cap(1));
- string value = boost::trim_copy(rx_keyval.cap(2));
+ for (auto kv : key_values)
+ {
+ static const regex rx_keyval("([^=]*)=(.+)", regex::extended);
+ smatch match;
+
+ if (regex_match(kv, match, rx_keyval))
+ {
+ string key = boost::trim_copy(match[1].str());
+ string value = boost::trim_copy(match[2].str());
result[key] = value;
}
- else {
+ else
+ {
cerr << "ERROR:" << "invalid userdata: expecting comma separated key=value pairs" << endl;
}
}
/*
- * Copyright (c) 2019 SUSE LLC
+ * Copyright (c) [2019-2020] SUSE LLC
*
* All Rights Reserved.
*
#include <iostream>
#include <vector>
#include <string>
+#include <regex>
using namespace std;
// fnmatch
#include <fnmatch.h>
-#include "snapper/Regex.h"
#include "snapper/XmlFile.h"
using namespace snapper;
static const int flags = 0;
res = fnmatch(pattern.c_str(), solvable.c_str(), flags) == 0;
}
- else {
- // POSIX Extended Regular Expression Syntax
- // The original Python implementation allows "foo" to match "foo-devel"
- snapper::Regex rx_pattern("^" + pattern, REG_EXTENDED | REG_NOSUB);
- res = rx_pattern.match(solvable);
+ else
+ {
+ try
+ {
+ // POSIX Extended Regular Expression Syntax
+ // The original Python implementation allows "foo" to match "foo-devel"
+ const regex rx_pattern("^" + pattern, regex::extended);
+ res = regex_search(solvable, rx_pattern);
+ }
+ catch (const regex_error& e)
+ {
+ throw std::runtime_error(string("Regex compilation error: ") + e.what());
+ }
}
log << "DEBUG:" << "-> " << res << endl;
return res;
};
std::string pattern;
Kind kind;
- bool important;
+ bool important = false;
static std::ostream& log;
SolvableMatcher(const std::string& apattern, Kind akind, bool aimportant)
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE regex
+
+#include <boost/test/unit_test.hpp>
+#include "solvable_matcher.h"
+
+
+static
+SolvableMatcher make_glob(const char* regex)
+{
+ bool is_important = false;
+ return SolvableMatcher(regex, SolvableMatcher::Kind::GLOB, is_important);
+}
+
+
+BOOST_AUTO_TEST_CASE(glob)
+{
+ BOOST_CHECK(make_glob("glibc").match("glibc"));
+ BOOST_CHECK(!make_glob("glibc").match("libc"));
+ BOOST_CHECK(!make_glob("glibc").match("glibc-locale"));
+
+ BOOST_CHECK(make_glob("glibc*").match("glibc"));
+ BOOST_CHECK(make_glob("glibc*").match("glibc-locale"));
+ BOOST_CHECK(!make_glob("glibc*").match("libc"));
+}
+
+
+static
+SolvableMatcher make_rx(const char* regex)
+{
+ bool is_important = false;
+ return SolvableMatcher(regex, SolvableMatcher::Kind::REGEX, is_important);
+}
+
+
+BOOST_AUTO_TEST_CASE(regex)
+{
+ BOOST_CHECK(make_rx("mypkg").match("mypkg"));
+ BOOST_CHECK(!make_rx("mypkg").match("yourpkg"));
+
+ // substrings don't count
+ BOOST_CHECK(!make_rx("foo").match("afool"));
+ // prefixes must match
+ BOOST_CHECK(make_rx("foo").match("fool"));
+ // explicit anchor is OK
+ BOOST_CHECK(make_rx("^foo").match("foo"));
+ // double anchor is also OK
+ BOOST_CHECK(make_rx("^^foo").match("foo"));
+ // exclude prefix matches
+ BOOST_CHECK(!make_rx("foo$").match("foo-devel"));
+
+ SolvableMatcher ror = make_rx("ruby[.0-9]+-rubygem-active(model|record)");
+ BOOST_CHECK(ror.match("ruby2.5-rubygem-activemodel-5.2"));
+ BOOST_CHECK(ror.match("ruby2.5-rubygem-activerecord-5_1"));
+}
/*
- * Copyright (c) 2019 SUSE LLC
+ * Copyright (c) [2019-2020] SUSE LLC
*
* All Rights Reserved.
*
#include <iostream>
#include <boost/algorithm/string/trim.hpp>
#include <string>
+#include <regex>
using namespace std;
-#include "snapper/Regex.h"
#include "zypp_plugin.h"
int ZyppPlugin::main() {
if (line.empty())
continue;
- static const snapper::Regex rx_word("^[A-Za-z0-9_]+$");
- if (rx_word.match(line)) {
+ static const regex rx_word("[A-Za-z0-9_]+", regex::extended);
+ if (regex_match(line, rx_word))
+ {
msg = Message();
msg.command = line;
state = State::Headers;
}
- else {
+ else
+ {
throw runtime_error("Plugin protocol error: expected a command. Got '" + line + "'");
}
}
return msg;
}
- else {
- static const snapper::Regex rx_header("^([A-Za-z0-9_]+):[ \t]*(.+)$");
- if (rx_header.match(line)) {
- string key = rx_header.cap(1);
- string value = rx_header.cap(2);
+ else
+ {
+ static const regex rx_header("([A-Za-z0-9_]+):[ \t]*(.+)", regex::extended);
+ smatch match;
+
+ if (regex_match(line, match, rx_header))
+ {
+ string key = match[1];
+ string value = match[2];
msg.headers[key] = value;
}
- else {
+ else
+ {
throw runtime_error("Plugin protocol error: expected a header or new line. Got '" + line + "'");
}
}