You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2016/08/11 20:06:59 UTC
[1/2] mesos git commit: Introduced linux capabilities API.
Repository: mesos
Updated Branches:
refs/heads/master 4c12c1e84 -> 8ab880272
Introduced linux capabilities API.
This change introduces basic API for linux capabilities. This is not a
comprehensive API but is strictly limited to the need for securing Mesos
containers using linux capabilities.
This patch is based on the work in https://reviews.apache.org/r/46370/.
Review: https://reviews.apache.org/r/50266/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/8ab88027
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/8ab88027
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/8ab88027
Branch: refs/heads/master
Commit: 8ab880272cbc020344855e4bb1d210bd6d648109
Parents: 98fe260
Author: Benjamin Bannier <be...@mesosphere.io>
Authored: Thu Aug 11 08:44:26 2016 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Thu Aug 11 13:06:48 2016 -0700
----------------------------------------------------------------------
src/CMakeLists.txt | 1 +
src/Makefile.am | 2 +
src/linux/capabilities.cpp | 397 ++++++++++++++++++++++++++++++++++++++++
src/linux/capabilities.hpp | 208 +++++++++++++++++++++
4 files changed, 608 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6088c26..4362d47 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -152,6 +152,7 @@ set(HDFS_SRC
set(LINUX_SRC
${LINUX_SRC}
+ linux/capabilities.cpp
linux/cgroups.cpp
linux/fs.cpp
linux/ldcache.cpp
http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 33706e5..d389e02 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1036,6 +1036,7 @@ libmesos_no_3rdparty_la_SOURCES += \
watcher/whitelist_watcher.hpp
MESOS_LINUX_FILES = \
+ linux/capabilities.cpp \
linux/cgroups.cpp \
linux/fs.cpp \
linux/ldcache.cpp \
@@ -1065,6 +1066,7 @@ MESOS_LINUX_FILES = \
slave/containerizer/mesos/provisioner/backends/overlay.cpp
MESOS_LINUX_FILES += \
+ linux/capabilities.hpp \
linux/cgroups.hpp \
linux/fs.hpp \
linux/ldcache.hpp \
http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/linux/capabilities.cpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.cpp b/src/linux/capabilities.cpp
new file mode 100644
index 0000000..5f21dd4
--- /dev/null
+++ b/src/linux/capabilities.cpp
@@ -0,0 +1,397 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+
+#include <linux/capability.h>
+
+#include <sys/prctl.h>
+
+#include <set>
+#include <string>
+
+#include <stout/numify.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+
+#include "linux/capabilities.hpp"
+
+using std::hex;
+using std::ostream;
+using std::set;
+using std::string;
+using std::vector;
+
+// We declare two functions provided in the libcap headers here to
+// prevent introduction of that build-time dependency.
+extern "C" {
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, const cap_user_data_t data);
+}
+
+
+namespace mesos {
+namespace internal {
+namespace capabilities {
+
+constexpr char PROC_CAP_LAST_CAP[] = "/proc/sys/kernel/cap_last_cap";
+constexpr int CAPABIILITY_PROTOBUF_OFFSET = 1000;
+
+
+// System call payload (for linux capability version 3).
+struct SyscallPayload
+{
+ __user_cap_header_struct head;
+ __user_cap_data_struct set[_LINUX_CAPABILITY_U32S_3];
+
+ SyscallPayload()
+ {
+ memset(this, 0, sizeof(SyscallPayload));
+ }
+
+ uint64_t effective()
+ {
+ uint64_t low = static_cast<uint64_t>(set[0].effective);
+ uint64_t high = static_cast<uint64_t>(set[1].effective) << 32;
+ return low | high;
+ }
+
+ uint64_t permitted()
+ {
+ uint64_t low = static_cast<uint64_t>(set[0].permitted);
+ uint64_t high = static_cast<uint64_t>(set[1].permitted) << 32;
+ return low | high;
+ }
+
+ uint64_t inheritable()
+ {
+ uint64_t low = static_cast<uint64_t>(set[0].inheritable);
+ uint64_t high = static_cast<uint64_t>(set[1].inheritable) << 32;
+ return low | high;
+ }
+
+ void setEffective(uint64_t effective)
+ {
+ set[0].effective = static_cast<uint32_t>(effective);
+ set[1].effective = static_cast<uint32_t>(effective >> 32);
+ }
+
+ void setPermitted(uint64_t permitted)
+ {
+ set[0].permitted = static_cast<uint32_t>(permitted);
+ set[1].permitted = static_cast<uint32_t>(permitted >> 32);
+ }
+
+ void setInheritable(uint64_t inheritable)
+ {
+ set[0].inheritable = static_cast<uint32_t>(inheritable);
+ set[1].inheritable = static_cast<uint32_t>(inheritable >> 32);
+ }
+};
+
+
+// Helper funciton to convert capability set to bitset.
+static uint64_t toCapabilityBitset(const Set<Capability>& capabilities)
+{
+ uint64_t result = 0;
+
+ for (int i = 0; i < MAX_CAPABILITY; i++) {
+ if (capabilities.count(static_cast<Capability>(i)) > 0) {
+ result |= (1ULL << i);
+ }
+ }
+
+ return result;
+}
+
+
+// Helper function to convert capability bitset to Set.
+static Set<Capability> toCapabilitySet(uint64_t bitset)
+{
+ Set<Capability> result;
+
+ for (int i = 0; i < MAX_CAPABILITY; i++) {
+ if ((bitset & (1ULL << i)) != 0) {
+ result.insert(Capability(i));
+ }
+ }
+
+ return result;
+}
+
+
+Set<Capability> ProcessCapabilities::get(const Type& type) const
+{
+ switch (type) {
+ case EFFECTIVE: return effective;
+ case INHERITABLE: return inheritable;
+ case PERMITTED: return permitted;
+ case BOUNDING: return bounding;
+ }
+
+ UNREACHABLE();
+}
+
+
+void ProcessCapabilities::set(
+ const Type& type,
+ const Set<Capability>& capabilities)
+{
+ switch (type) {
+ case EFFECTIVE: effective = capabilities;
+ case PERMITTED: permitted = capabilities;
+ case INHERITABLE: inheritable = capabilities;
+ case BOUNDING: bounding = capabilities;
+ default: UNREACHABLE();
+ }
+}
+
+
+Try<Capabilities> Capabilities::create()
+{
+ // Check for compatible linux capability version.
+ SyscallPayload payload;
+
+ if (capget(&payload.head, nullptr)) {
+ return ErrnoError("Failed to get linux capability version");
+ }
+
+ if (payload.head.version != _LINUX_CAPABILITY_VERSION_3) {
+ return Error(
+ "Unsupported linux capabilities version: " +
+ stringify(payload.head.version));
+ }
+
+ // Read and check the maximum capability value.
+ Try<string> _lastCap = os::read(PROC_CAP_LAST_CAP);
+ if (_lastCap.isError()) {
+ return Error(
+ "Failed to read '" + string(PROC_CAP_LAST_CAP) + "': " +
+ _lastCap.error());
+ }
+
+ Try<int> lastCap = numify<int>(strings::trim(
+ _lastCap.get(),
+ strings::SUFFIX,
+ "\n"));
+
+ if (lastCap.isError()) {
+ return Error(
+ "Failed to parse system last capability value '" +
+ _lastCap.get() + "': " + lastCap.error());
+ }
+
+ if (lastCap.get() >= MAX_CAPABILITY) {
+ return Error(
+ "System last capability value '" + stringify(lastCap.get()) +
+ "' is greater than maximum supported number of capabilities '" +
+ stringify(MAX_CAPABILITY) + "'");
+ }
+
+ return Capabilities(lastCap.get());
+}
+
+
+Try<ProcessCapabilities> Capabilities::get() const
+{
+ SyscallPayload payload;
+
+ payload.head.version = _LINUX_CAPABILITY_VERSION_3;
+ payload.head.pid = 0;
+
+ if (capget(&payload.head, &payload.set[0])) {
+ return ErrnoError("Failed to get capabilities");
+ }
+
+ ProcessCapabilities capabilities;
+
+ capabilities.effective = toCapabilitySet(payload.effective());
+ capabilities.permitted = toCapabilitySet(payload.permitted());
+ capabilities.inheritable = toCapabilitySet(payload.inheritable());
+
+ // TODO(jojy): Add support for BOUNDING capabilities.
+
+ return capabilities;
+}
+
+
+// We do two separate operations:
+// 1. Set the `bounding` capabilities for the process.
+// 2. Set the `effective`, `permitted` and `inheritable` capabilities.
+//
+// TODO(jojy): Is there a way to make this atomic? Ideally, we would
+// like to rollback any changes if any of the operation fails.
+Try<Nothing> Capabilities::set(const ProcessCapabilities& capabilities)
+{
+ // NOTE: We can only drop capabilities in the bounding set.
+ for (int i = 0; i <= lastCap; i++) {
+ if (capabilities.bounding.count(Capability(i)) > 0) {
+ continue;
+ }
+
+ VLOG(1) << "Dropping capability " << Capability(i);
+
+ if (prctl(PR_CAPBSET_DROP, i, 1) < 0) {
+ return ErrnoError(
+ "Failed to drop capability: "
+ "PR_CAPBSET_DROP failed for the process");
+ }
+ }
+
+ SyscallPayload payload;
+
+ payload.head.version = _LINUX_CAPABILITY_VERSION_3;
+ payload.head.pid = 0;
+
+ payload.setEffective(toCapabilityBitset(capabilities.effective));
+ payload.setPermitted(toCapabilityBitset(capabilities.permitted));
+ payload.setInheritable(toCapabilityBitset(capabilities.inheritable));
+
+ if (capset(&payload.head, &payload.set[0])) {
+ return ErrnoError("Failed to set capabilities");
+ }
+
+ return Nothing();
+}
+
+
+Try<Nothing> Capabilities::keepCapabilitiesOnSetUid()
+{
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+ return ErrnoError("Failed to set PR_SET_KEEPCAPS for the process");
+ }
+
+ return Nothing();
+}
+
+
+Set<Capability> Capabilities::getAllSupportedCapabilities()
+{
+ Set<Capability> result;
+
+ for (int i = 0; i <= lastCap; i++) {
+ result.insert(Capability(i));
+ }
+
+ return result;
+}
+
+
+Capability convert(const CapabilityInfo::Capability& capability)
+{
+ int value = capability - CAPABIILITY_PROTOBUF_OFFSET;
+
+ CHECK_LE(0, value);
+ CHECK_GT(MAX_CAPABILITY, value);
+
+ return static_cast<Capability>(value);
+}
+
+
+CapabilityInfo convert(const Set<Capability>& capabilities)
+{
+ CapabilityInfo capabilityInfo;
+
+ foreach (const Capability& capability, capabilities) {
+ capabilityInfo.add_capabilities(static_cast<CapabilityInfo::Capability>(
+ capability + CAPABIILITY_PROTOBUF_OFFSET));
+ }
+
+ return capabilityInfo;
+}
+
+
+ostream& operator<<(ostream& stream, const Capability& capability)
+{
+ switch (capability) {
+ case CHOWN: return stream << "CHOWN";
+ case DAC_OVERRIDE: return stream << "DAC_OVERRIDE";
+ case DAC_READ_SEARCH: return stream << "DAC_READ_SEARCH";
+ case FOWNER: return stream << "FOWNER";
+ case FSETID: return stream << "FSETID";
+ case KILL: return stream << "KILL";
+ case SETGID: return stream << "SETGID";
+ case SETUID: return stream << "SETUID";
+ case SETPCAP: return stream << "SETPCAP";
+ case LINUX_IMMUTABLE: return stream << "LINUX_IMMUTABLE";
+ case NET_BIND_SERVICE: return stream << "NET_BIND_SERVICE";
+ case NET_BROADCAST: return stream << "NET_BROADCAST";
+ case NET_ADMIN: return stream << "NET_ADMIN";
+ case NET_RAW: return stream << "NET_RAW";
+ case IPC_LOCK: return stream << "IPC_LOCK";
+ case IPC_OWNER: return stream << "IPC_OWNER";
+ case SYS_MODULE: return stream << "SYS_MODULE";
+ case SYS_RAWIO: return stream << "SYS_RAWIO";
+ case SYS_CHROOT: return stream << "SYS_CHROOT";
+ case SYS_PTRACE: return stream << "SYS_PTRACE";
+ case SYS_PACCT: return stream << "SYS_PACCT";
+ case SYS_ADMIN: return stream << "SYS_ADMIN";
+ case SYS_BOOT: return stream << "SYS_BOOT";
+ case SYS_NICE: return stream << "SYS_NICE";
+ case SYS_RESOURCE: return stream << "SYS_RESOURCE";
+ case SYS_TIME: return stream << "SYS_TIME";
+ case SYS_TTY_CONFIG: return stream << "SYS_TTY_CONFIG";
+ case MKNOD: return stream << "MKNOD";
+ case LEASE: return stream << "LEASE";
+ case AUDIT_WRITE: return stream << "AUDIT_WRITE";
+ case AUDIT_CONTROL: return stream << "AUDIT_CONTROL";
+ case SETFCAP: return stream << "SETFCAP";
+ case MAC_OVERRIDE: return stream << "MAC_OVERRIDE";
+ case MAC_ADMIN: return stream << "MAC_ADMIN";
+ case SYSLOG: return stream << "SYSLOG";
+ case WAKE_ALARM: return stream << "WAKE_ALARM";
+ case BLOCK_SUSPEND: return stream << "BLOCK_SUSPEND";
+ case AUDIT_READ: return stream << "AUDIT_READ";
+ case MAX_CAPABILITY: UNREACHABLE();
+ }
+
+ UNREACHABLE();
+}
+
+
+ostream& operator<<(ostream& stream, const Type& type)
+{
+ switch (type) {
+ case EFFECTIVE: return stream << "eff";
+ case INHERITABLE: return stream << "inh";
+ case PERMITTED: return stream << "perm";
+ case BOUNDING: return stream << "bnd";
+ }
+
+ UNREACHABLE();
+}
+
+
+ostream& operator<<(ostream& stream, const Set<Capability>& capabilities)
+{
+ return stream << stringify(capabilities);
+}
+
+
+ostream& operator<<(ostream& stream, const ProcessCapabilities& capabilities)
+{
+ return stream
+ << "{"
+ << EFFECTIVE << ": " << capabilities.effective << ", "
+ << PERMITTED << ": " << capabilities.permitted << ", "
+ << INHERITABLE << ": " << capabilities.inheritable << ", "
+ << BOUNDING << ": " << capabilities.bounding
+ << "}";
+}
+
+} // namespace capabilities {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/8ab88027/src/linux/capabilities.hpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.hpp b/src/linux/capabilities.hpp
new file mode 100644
index 0000000..fd7a571
--- /dev/null
+++ b/src/linux/capabilities.hpp
@@ -0,0 +1,208 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __LINUX_CAPABILITIES_HPP__
+#define __LINUX_CAPABILITIES_HPP__
+
+#include <string>
+#include <vector>
+
+#include <stout/flags.hpp>
+#include <stout/nothing.hpp>
+#include <stout/protobuf.hpp>
+#include <stout/set.hpp>
+#include <stout/try.hpp>
+
+#include <mesos/mesos.hpp>
+
+namespace mesos {
+namespace internal {
+namespace capabilities {
+
+// Superset of all capabilities. This is the set currently supported
+// by linux (kernel 4.0).
+enum Capability : int
+{
+ CHOWN = 0,
+ DAC_OVERRIDE = 1,
+ DAC_READ_SEARCH = 2,
+ FOWNER = 3,
+ FSETID = 4,
+ KILL = 5,
+ SETGID = 6,
+ SETUID = 7,
+ SETPCAP = 8,
+ LINUX_IMMUTABLE = 9,
+ NET_BIND_SERVICE = 10,
+ NET_BROADCAST = 11,
+ NET_ADMIN = 12,
+ NET_RAW = 13,
+ IPC_LOCK = 14,
+ IPC_OWNER = 15,
+ SYS_MODULE = 16,
+ SYS_RAWIO = 17,
+ SYS_CHROOT = 18,
+ SYS_PTRACE = 19,
+ SYS_PACCT = 20,
+ SYS_ADMIN = 21,
+ SYS_BOOT = 22,
+ SYS_NICE = 23,
+ SYS_RESOURCE = 24,
+ SYS_TIME = 25,
+ SYS_TTY_CONFIG = 26,
+ MKNOD = 27,
+ LEASE = 28,
+ AUDIT_WRITE = 29,
+ AUDIT_CONTROL = 30,
+ SETFCAP = 31,
+ MAC_OVERRIDE = 32,
+ MAC_ADMIN = 33,
+ SYSLOG = 34,
+ WAKE_ALARM = 35,
+ BLOCK_SUSPEND = 36,
+ AUDIT_READ = 37,
+ MAX_CAPABILITY = 38,
+};
+
+
+enum Type
+{
+ EFFECTIVE,
+ PERMITTED,
+ INHERITABLE,
+ BOUNDING,
+};
+
+
+/**
+ * Encapsulation of capability value sets.
+ */
+class ProcessCapabilities
+{
+public:
+ Set<Capability> get(const Type& type) const;
+ void set(const Type& type, const Set<Capability>& capabilities);
+
+private:
+ friend class Capabilities;
+
+ friend std::ostream& operator<<(
+ std::ostream& stream,
+ const ProcessCapabilities& set);
+
+ // Disallow default constructor.
+ ProcessCapabilities() {}
+
+ Set<Capability> effective;
+ Set<Capability> permitted;
+ Set<Capability> inheritable;
+ Set<Capability> bounding;
+};
+
+
+/**
+ * Provides wrapper for the linux process capabilities interface.
+ * Note: This is a class instead of an interface because it has state
+ * associated with it.
+ *
+ * TODO(jojy): Currently we only support linux capabilities. Consider
+ * refactoring the interface so that we can support a generic
+ * interface which can be used for other OSes(BSD, Windows etc).
+ */
+class Capabilities
+{
+public:
+ /**
+ * Factory method to create Capabilities object.
+ *
+ * @return `Capabilities` on success;
+ * Error on falure. Failure conditions could be:
+ * - Error getting system information (e.g, version).
+ * - Unsupported linux kernel capabilities version.
+ * - Maximum capability supported by kernel exceeds the
+ * ones defined in the enum `Capabilities`.
+ */
+ static Try<Capabilities> create();
+
+ /**
+ * Gets capability set for the calling process.
+ *
+ * @return ProcessCapabilities on success.
+ * Error on failure.
+ */
+ Try<ProcessCapabilities> get() const;
+
+ /**
+ * Sets capabilities for the calling process.
+ *
+ * @param `ProcessCapabilities` to be set for the process.
+ * @return Nothing on success.
+ * Error on failure.
+ */
+ Try<Nothing> set(const ProcessCapabilities& processCapabilities);
+
+ /**
+ * Process control interface to enforce keeping the parent process's
+ * capabilities after a change in uid/gid.
+ *
+ * @return Nothing on success.
+ * Error on failure.
+ */
+ Try<Nothing> keepCapabilitiesOnSetUid();
+
+ /**
+ * Get all capabilities supported by the system.
+ *
+ * @return the set of supported capabilities.
+ */
+ Set<Capability> getAllSupportedCapabilities();
+
+private:
+ explicit Capabilities(int _lastCap) : lastCap(_lastCap) {}
+
+ // Maximum count of capabilities supported by the system.
+ const int lastCap;
+};
+
+
+Capability convert(const CapabilityInfo::Capability& capabilityInfo);
+CapabilityInfo convert(const Set<Capability>& capabilitySet);
+
+
+std::ostream& operator<<(
+ std::ostream& stream,
+ const Capability& capability);
+
+
+std::ostream& operator<<(
+ std::ostream& stream,
+ const Type& type);
+
+
+std::ostream& operator<<(
+ std::ostream& stream,
+ const Set<Capability>& capabilities);
+
+
+std::ostream& operator<<(
+ std::ostream& stream,
+ const ProcessCapabilities& capabilities);
+
+} // namespace capabilities {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __LINUX_CAPABILITIES_HPP__
[2/2] mesos git commit: Added stringify for Set in stout.
Posted by ji...@apache.org.
Added stringify for Set in stout.
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/98fe2606
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/98fe2606
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/98fe2606
Branch: refs/heads/master
Commit: 98fe2606040e72c73240026dcababe6dc0f06b1d
Parents: 4c12c1e
Author: Jie Yu <yu...@gmail.com>
Authored: Thu Aug 11 13:03:42 2016 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Thu Aug 11 13:06:48 2016 -0700
----------------------------------------------------------------------
3rdparty/stout/include/stout/stringify.hpp | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/98fe2606/3rdparty/stout/include/stout/stringify.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/stout/include/stout/stringify.hpp b/3rdparty/stout/include/stout/stringify.hpp
index 6b6e788..b4d3e61 100644
--- a/3rdparty/stout/include/stout/stringify.hpp
+++ b/3rdparty/stout/include/stout/stringify.hpp
@@ -23,6 +23,7 @@
#include "abort.hpp"
#include "hashmap.hpp"
+#include "set.hpp"
template <typename T>
std::string stringify(T t)
@@ -61,6 +62,23 @@ std::string stringify(const std::set<T>& set)
template <typename T>
+std::string stringify(const Set<T>& set)
+{
+ std::ostringstream out;
+ out << "{ ";
+ typename Set<T>::const_iterator iterator = set.begin();
+ while (iterator != set.end()) {
+ out << stringify(*iterator);
+ if (++iterator != set.end()) {
+ out << ", ";
+ }
+ }
+ out << " }";
+ return out.str();
+}
+
+
+template <typename T>
std::string stringify(const std::list<T>& list)
{
std::ostringstream out;