summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/.gitignore2
-rw-r--r--utils/Kyuafile24
-rw-r--r--utils/Makefile.am.inc133
-rw-r--r--utils/auto_array.hpp102
-rw-r--r--utils/auto_array.ipp227
-rw-r--r--utils/auto_array_fwd.hpp43
-rw-r--r--utils/auto_array_test.cpp312
-rw-r--r--utils/cmdline/Kyuafile11
-rw-r--r--utils/cmdline/Makefile.am.inc96
-rw-r--r--utils/cmdline/base_command.cpp201
-rw-r--r--utils/cmdline/base_command.hpp162
-rw-r--r--utils/cmdline/base_command.ipp104
-rw-r--r--utils/cmdline/base_command_fwd.hpp47
-rw-r--r--utils/cmdline/base_command_test.cpp295
-rw-r--r--utils/cmdline/commands_map.hpp96
-rw-r--r--utils/cmdline/commands_map.ipp161
-rw-r--r--utils/cmdline/commands_map_fwd.hpp45
-rw-r--r--utils/cmdline/commands_map_test.cpp140
-rw-r--r--utils/cmdline/exceptions.cpp175
-rw-r--r--utils/cmdline/exceptions.hpp109
-rw-r--r--utils/cmdline/exceptions_test.cpp83
-rw-r--r--utils/cmdline/globals.cpp78
-rw-r--r--utils/cmdline/globals.hpp48
-rw-r--r--utils/cmdline/globals_test.cpp77
-rw-r--r--utils/cmdline/options.cpp605
-rw-r--r--utils/cmdline/options.hpp237
-rw-r--r--utils/cmdline/options_fwd.hpp51
-rw-r--r--utils/cmdline/options_test.cpp526
-rw-r--r--utils/cmdline/parser.cpp385
-rw-r--r--utils/cmdline/parser.hpp85
-rw-r--r--utils/cmdline/parser.ipp83
-rw-r--r--utils/cmdline/parser_fwd.hpp58
-rw-r--r--utils/cmdline/parser_test.cpp688
-rw-r--r--utils/cmdline/ui.cpp276
-rw-r--r--utils/cmdline/ui.hpp79
-rw-r--r--utils/cmdline/ui_fwd.hpp45
-rw-r--r--utils/cmdline/ui_mock.cpp114
-rw-r--r--utils/cmdline/ui_mock.hpp78
-rw-r--r--utils/cmdline/ui_test.cpp424
-rw-r--r--utils/config/Kyuafile10
-rw-r--r--utils/config/Makefile.am.inc87
-rw-r--r--utils/config/exceptions.cpp149
-rw-r--r--utils/config/exceptions.hpp106
-rw-r--r--utils/config/exceptions_test.cpp133
-rw-r--r--utils/config/keys.cpp70
-rw-r--r--utils/config/keys.hpp52
-rw-r--r--utils/config/keys_fwd.hpp51
-rw-r--r--utils/config/keys_test.cpp114
-rw-r--r--utils/config/lua_module.cpp282
-rw-r--r--utils/config/lua_module.hpp50
-rw-r--r--utils/config/lua_module_test.cpp474
-rw-r--r--utils/config/nodes.cpp589
-rw-r--r--utils/config/nodes.hpp272
-rw-r--r--utils/config/nodes.ipp408
-rw-r--r--utils/config/nodes_fwd.hpp70
-rw-r--r--utils/config/nodes_test.cpp695
-rw-r--r--utils/config/parser.cpp181
-rw-r--r--utils/config/parser.hpp95
-rw-r--r--utils/config/parser_fwd.hpp45
-rw-r--r--utils/config/parser_test.cpp252
-rw-r--r--utils/config/tree.cpp338
-rw-r--r--utils/config/tree.hpp128
-rw-r--r--utils/config/tree.ipp156
-rw-r--r--utils/config/tree_fwd.hpp52
-rw-r--r--utils/config/tree_test.cpp1086
-rw-r--r--utils/datetime.cpp613
-rw-r--r--utils/datetime.hpp140
-rw-r--r--utils/datetime_fwd.hpp46
-rw-r--r--utils/datetime_test.cpp593
-rw-r--r--utils/defs.hpp.in57
-rw-r--r--utils/env.cpp200
-rw-r--r--utils/env.hpp58
-rw-r--r--utils/env_test.cpp167
-rw-r--r--utils/format/Kyuafile7
-rw-r--r--utils/format/Makefile.am.inc59
-rw-r--r--utils/format/containers.hpp66
-rw-r--r--utils/format/containers.ipp138
-rw-r--r--utils/format/containers_test.cpp190
-rw-r--r--utils/format/exceptions.cpp110
-rw-r--r--utils/format/exceptions.hpp84
-rw-r--r--utils/format/exceptions_test.cpp74
-rw-r--r--utils/format/formatter.cpp293
-rw-r--r--utils/format/formatter.hpp123
-rw-r--r--utils/format/formatter.ipp76
-rw-r--r--utils/format/formatter_fwd.hpp45
-rw-r--r--utils/format/formatter_test.cpp265
-rw-r--r--utils/format/macros.hpp58
-rw-r--r--utils/fs/Kyuafile10
-rw-r--r--utils/fs/Makefile.am.inc84
-rw-r--r--utils/fs/auto_cleaners.cpp261
-rw-r--r--utils/fs/auto_cleaners.hpp89
-rw-r--r--utils/fs/auto_cleaners_fwd.hpp46
-rw-r--r--utils/fs/auto_cleaners_test.cpp167
-rw-r--r--utils/fs/directory.cpp360
-rw-r--r--utils/fs/directory.hpp120
-rw-r--r--utils/fs/directory_fwd.hpp55
-rw-r--r--utils/fs/directory_test.cpp190
-rw-r--r--utils/fs/exceptions.cpp162
-rw-r--r--utils/fs/exceptions.hpp110
-rw-r--r--utils/fs/exceptions_test.cpp95
-rw-r--r--utils/fs/lua_module.cpp340
-rw-r--r--utils/fs/lua_module.hpp54
-rw-r--r--utils/fs/lua_module_test.cpp376
-rw-r--r--utils/fs/operations.cpp803
-rw-r--r--utils/fs/operations.hpp72
-rw-r--r--utils/fs/operations_test.cpp826
-rw-r--r--utils/fs/path.cpp303
-rw-r--r--utils/fs/path.hpp87
-rw-r--r--utils/fs/path_fwd.hpp45
-rw-r--r--utils/fs/path_test.cpp277
-rw-r--r--utils/logging/Kyuafile6
-rw-r--r--utils/logging/Makefile.am.inc53
-rw-r--r--utils/logging/macros.hpp68
-rw-r--r--utils/logging/macros_test.cpp115
-rw-r--r--utils/logging/operations.cpp303
-rw-r--r--utils/logging/operations.hpp54
-rw-r--r--utils/logging/operations_fwd.hpp54
-rw-r--r--utils/logging/operations_test.cpp354
-rw-r--r--utils/memory.cpp158
-rw-r--r--utils/memory.hpp45
-rw-r--r--utils/memory_test.cpp63
-rw-r--r--utils/noncopyable.hpp75
-rw-r--r--utils/optional.hpp90
-rw-r--r--utils/optional.ipp252
-rw-r--r--utils/optional_fwd.hpp61
-rw-r--r--utils/optional_test.cpp285
-rw-r--r--utils/passwd.cpp194
-rw-r--r--utils/passwd.hpp72
-rw-r--r--utils/passwd_fwd.hpp45
-rw-r--r--utils/passwd_test.cpp179
-rw-r--r--utils/process/.gitignore1
-rw-r--r--utils/process/Kyuafile13
-rw-r--r--utils/process/Makefile.am.inc113
-rw-r--r--utils/process/child.cpp385
-rw-r--r--utils/process/child.hpp113
-rw-r--r--utils/process/child.ipp110
-rw-r--r--utils/process/child_fwd.hpp45
-rw-r--r--utils/process/child_test.cpp846
-rw-r--r--utils/process/deadline_killer.cpp54
-rw-r--r--utils/process/deadline_killer.hpp58
-rw-r--r--utils/process/deadline_killer_fwd.hpp45
-rw-r--r--utils/process/deadline_killer_test.cpp108
-rw-r--r--utils/process/exceptions.cpp91
-rw-r--r--utils/process/exceptions.hpp78
-rw-r--r--utils/process/exceptions_test.cpp63
-rw-r--r--utils/process/executor.cpp869
-rw-r--r--utils/process/executor.hpp231
-rw-r--r--utils/process/executor.ipp182
-rw-r--r--utils/process/executor_fwd.hpp49
-rw-r--r--utils/process/executor_test.cpp940
-rw-r--r--utils/process/fdstream.cpp76
-rw-r--r--utils/process/fdstream.hpp66
-rw-r--r--utils/process/fdstream_fwd.hpp45
-rw-r--r--utils/process/fdstream_test.cpp73
-rw-r--r--utils/process/helpers.cpp74
-rw-r--r--utils/process/isolation.cpp207
-rw-r--r--utils/process/isolation.hpp60
-rw-r--r--utils/process/isolation_test.cpp622
-rw-r--r--utils/process/operations.cpp273
-rw-r--r--utils/process/operations.hpp56
-rw-r--r--utils/process/operations_fwd.hpp49
-rw-r--r--utils/process/operations_test.cpp471
-rw-r--r--utils/process/status.cpp200
-rw-r--r--utils/process/status.hpp84
-rw-r--r--utils/process/status_fwd.hpp45
-rw-r--r--utils/process/status_test.cpp209
-rw-r--r--utils/process/system.cpp59
-rw-r--r--utils/process/system.hpp66
-rw-r--r--utils/process/systembuf.cpp152
-rw-r--r--utils/process/systembuf.hpp71
-rw-r--r--utils/process/systembuf_fwd.hpp45
-rw-r--r--utils/process/systembuf_test.cpp166
-rw-r--r--utils/sanity.cpp194
-rw-r--r--utils/sanity.hpp183
-rw-r--r--utils/sanity_fwd.hpp52
-rw-r--r--utils/sanity_test.cpp322
-rw-r--r--utils/signals/Kyuafile9
-rw-r--r--utils/signals/Makefile.am.inc73
-rw-r--r--utils/signals/exceptions.cpp102
-rw-r--r--utils/signals/exceptions.hpp83
-rw-r--r--utils/signals/exceptions_test.cpp73
-rw-r--r--utils/signals/interrupts.cpp309
-rw-r--r--utils/signals/interrupts.hpp83
-rw-r--r--utils/signals/interrupts_fwd.hpp46
-rw-r--r--utils/signals/interrupts_test.cpp266
-rw-r--r--utils/signals/misc.cpp106
-rw-r--r--utils/signals/misc.hpp49
-rw-r--r--utils/signals/misc_test.cpp133
-rw-r--r--utils/signals/programmer.cpp138
-rw-r--r--utils/signals/programmer.hpp63
-rw-r--r--utils/signals/programmer_fwd.hpp49
-rw-r--r--utils/signals/programmer_test.cpp140
-rw-r--r--utils/signals/timer.cpp547
-rw-r--r--utils/signals/timer.hpp86
-rw-r--r--utils/signals/timer_fwd.hpp45
-rw-r--r--utils/signals/timer_test.cpp426
-rw-r--r--utils/sqlite/Kyuafile9
-rw-r--r--utils/sqlite/Makefile.am.inc82
-rw-r--r--utils/sqlite/c_gate.cpp83
-rw-r--r--utils/sqlite/c_gate.hpp74
-rw-r--r--utils/sqlite/c_gate_fwd.hpp45
-rw-r--r--utils/sqlite/c_gate_test.cpp96
-rw-r--r--utils/sqlite/database.cpp328
-rw-r--r--utils/sqlite/database.hpp111
-rw-r--r--utils/sqlite/database_fwd.hpp45
-rw-r--r--utils/sqlite/database_test.cpp287
-rw-r--r--utils/sqlite/exceptions.cpp175
-rw-r--r--utils/sqlite/exceptions.hpp94
-rw-r--r--utils/sqlite/exceptions_test.cpp129
-rw-r--r--utils/sqlite/statement.cpp621
-rw-r--r--utils/sqlite/statement.hpp137
-rw-r--r--utils/sqlite/statement.ipp52
-rw-r--r--utils/sqlite/statement_fwd.hpp57
-rw-r--r--utils/sqlite/statement_test.cpp784
-rw-r--r--utils/sqlite/test_utils.hpp151
-rw-r--r--utils/sqlite/transaction.cpp142
-rw-r--r--utils/sqlite/transaction.hpp69
-rw-r--r--utils/sqlite/transaction_fwd.hpp45
-rw-r--r--utils/sqlite/transaction_test.cpp135
-rw-r--r--utils/stacktrace.cpp370
-rw-r--r--utils/stacktrace.hpp68
-rw-r--r--utils/stacktrace_helper.cpp36
-rw-r--r--utils/stacktrace_test.cpp620
-rw-r--r--utils/stream.cpp149
-rw-r--r--utils/stream.hpp57
-rw-r--r--utils/stream_test.cpp157
-rw-r--r--utils/test_utils.ipp113
-rw-r--r--utils/text/Kyuafile9
-rw-r--r--utils/text/Makefile.am.inc74
-rw-r--r--utils/text/exceptions.cpp91
-rw-r--r--utils/text/exceptions.hpp77
-rw-r--r--utils/text/exceptions_test.cpp76
-rw-r--r--utils/text/operations.cpp261
-rw-r--r--utils/text/operations.hpp68
-rw-r--r--utils/text/operations.ipp91
-rw-r--r--utils/text/operations_test.cpp435
-rw-r--r--utils/text/regex.cpp302
-rw-r--r--utils/text/regex.hpp92
-rw-r--r--utils/text/regex_fwd.hpp46
-rw-r--r--utils/text/regex_test.cpp177
-rw-r--r--utils/text/table.cpp428
-rw-r--r--utils/text/table.hpp125
-rw-r--r--utils/text/table_fwd.hpp58
-rw-r--r--utils/text/table_test.cpp413
-rw-r--r--utils/text/templates.cpp764
-rw-r--r--utils/text/templates.hpp122
-rw-r--r--utils/text/templates_fwd.hpp45
-rw-r--r--utils/text/templates_test.cpp1001
-rw-r--r--utils/units.cpp172
-rw-r--r--utils/units.hpp96
-rw-r--r--utils/units_fwd.hpp45
-rw-r--r--utils/units_test.cpp248
252 files changed, 45147 insertions, 0 deletions
diff --git a/utils/.gitignore b/utils/.gitignore
new file mode 100644
index 000000000000..b33d720f27a4
--- /dev/null
+++ b/utils/.gitignore
@@ -0,0 +1,2 @@
+defs.hpp
+stacktrace_helper
diff --git a/utils/Kyuafile b/utils/Kyuafile
new file mode 100644
index 000000000000..042ad77a3fe4
--- /dev/null
+++ b/utils/Kyuafile
@@ -0,0 +1,24 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="auto_array_test"}
+atf_test_program{name="datetime_test"}
+atf_test_program{name="env_test"}
+atf_test_program{name="memory_test"}
+atf_test_program{name="optional_test"}
+atf_test_program{name="passwd_test"}
+atf_test_program{name="sanity_test"}
+atf_test_program{name="stacktrace_test"}
+atf_test_program{name="stream_test"}
+atf_test_program{name="units_test"}
+
+include("cmdline/Kyuafile")
+include("config/Kyuafile")
+include("format/Kyuafile")
+include("fs/Kyuafile")
+include("logging/Kyuafile")
+include("process/Kyuafile")
+include("signals/Kyuafile")
+include("sqlite/Kyuafile")
+include("text/Kyuafile")
diff --git a/utils/Makefile.am.inc b/utils/Makefile.am.inc
new file mode 100644
index 000000000000..d6690bdbecde
--- /dev/null
+++ b/utils/Makefile.am.inc
@@ -0,0 +1,133 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS =
+UTILS_LIBS = libutils.a
+
+noinst_LIBRARIES += libutils.a
+libutils_a_CPPFLAGS = -DGDB=\"$(GDB)\"
+libutils_a_SOURCES = utils/auto_array.hpp
+libutils_a_SOURCES += utils/auto_array.ipp
+libutils_a_SOURCES += utils/auto_array_fwd.hpp
+libutils_a_SOURCES += utils/datetime.cpp
+libutils_a_SOURCES += utils/datetime.hpp
+libutils_a_SOURCES += utils/datetime_fwd.hpp
+libutils_a_SOURCES += utils/env.hpp
+libutils_a_SOURCES += utils/env.cpp
+libutils_a_SOURCES += utils/memory.hpp
+libutils_a_SOURCES += utils/memory.cpp
+libutils_a_SOURCES += utils/noncopyable.hpp
+libutils_a_SOURCES += utils/optional.hpp
+libutils_a_SOURCES += utils/optional_fwd.hpp
+libutils_a_SOURCES += utils/optional.ipp
+libutils_a_SOURCES += utils/passwd.cpp
+libutils_a_SOURCES += utils/passwd.hpp
+libutils_a_SOURCES += utils/passwd_fwd.hpp
+libutils_a_SOURCES += utils/sanity.cpp
+libutils_a_SOURCES += utils/sanity.hpp
+libutils_a_SOURCES += utils/sanity_fwd.hpp
+libutils_a_SOURCES += utils/stacktrace.cpp
+libutils_a_SOURCES += utils/stacktrace.hpp
+libutils_a_SOURCES += utils/stream.cpp
+libutils_a_SOURCES += utils/stream.hpp
+libutils_a_SOURCES += utils/units.cpp
+libutils_a_SOURCES += utils/units.hpp
+libutils_a_SOURCES += utils/units_fwd.hpp
+nodist_libutils_a_SOURCES = utils/defs.hpp
+
+EXTRA_DIST += utils/test_utils.ipp
+
+if WITH_ATF
+tests_utilsdir = $(pkgtestsdir)/utils
+
+tests_utils_DATA = utils/Kyuafile
+EXTRA_DIST += $(tests_utils_DATA)
+
+tests_utils_PROGRAMS = utils/auto_array_test
+utils_auto_array_test_SOURCES = utils/auto_array_test.cpp
+utils_auto_array_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_auto_array_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/datetime_test
+utils_datetime_test_SOURCES = utils/datetime_test.cpp
+utils_datetime_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_datetime_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/env_test
+utils_env_test_SOURCES = utils/env_test.cpp
+utils_env_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_env_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/memory_test
+utils_memory_test_SOURCES = utils/memory_test.cpp
+utils_memory_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_memory_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/optional_test
+utils_optional_test_SOURCES = utils/optional_test.cpp
+utils_optional_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_optional_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/passwd_test
+utils_passwd_test_SOURCES = utils/passwd_test.cpp
+utils_passwd_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_passwd_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/sanity_test
+utils_sanity_test_SOURCES = utils/sanity_test.cpp
+utils_sanity_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sanity_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/stacktrace_helper
+utils_stacktrace_helper_SOURCES = utils/stacktrace_helper.cpp
+
+tests_utils_PROGRAMS += utils/stacktrace_test
+utils_stacktrace_test_SOURCES = utils/stacktrace_test.cpp
+utils_stacktrace_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_stacktrace_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/stream_test
+utils_stream_test_SOURCES = utils/stream_test.cpp
+utils_stream_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_stream_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/units_test
+utils_units_test_SOURCES = utils/units_test.cpp
+utils_units_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_units_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
+
+include utils/cmdline/Makefile.am.inc
+include utils/config/Makefile.am.inc
+include utils/format/Makefile.am.inc
+include utils/fs/Makefile.am.inc
+include utils/logging/Makefile.am.inc
+include utils/process/Makefile.am.inc
+include utils/signals/Makefile.am.inc
+include utils/sqlite/Makefile.am.inc
+include utils/text/Makefile.am.inc
diff --git a/utils/auto_array.hpp b/utils/auto_array.hpp
new file mode 100644
index 000000000000..0cc3d0e0afd5
--- /dev/null
+++ b/utils/auto_array.hpp
@@ -0,0 +1,102 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/auto_array.hpp
+/// Provides the utils::auto_array class.
+///
+/// The class is provided as a separate module on its own to minimize
+/// header-inclusion side-effects.
+
+#if !defined(UTILS_AUTO_ARRAY_HPP)
+#define UTILS_AUTO_ARRAY_HPP
+
+#include "utils/auto_array_fwd.hpp"
+
+#include <cstddef>
+
+namespace utils {
+
+
+namespace detail {
+
+
+/// Wrapper class to provide reference semantics for utils::auto_array.
+///
+/// This class is internally used, for example, to allow returning a
+/// utils::auto_array from a function.
+template< class T >
+class auto_array_ref {
+ /// Internal pointer to the dynamically-allocated array.
+ T* _ptr;
+
+ template< class > friend class utils::auto_array;
+
+public:
+ explicit auto_array_ref(T*);
+};
+
+
+} // namespace detail
+
+
+/// A simple smart pointer for arrays providing strict ownership semantics.
+///
+/// This class is the counterpart of std::auto_ptr for arrays. The semantics of
+/// the API of this class are the same as those of std::auto_ptr.
+///
+/// The wrapped pointer must be NULL or must have been allocated using operator
+/// new[].
+template< class T >
+class auto_array {
+ /// Internal pointer to the dynamically-allocated array.
+ T* _ptr;
+
+public:
+ auto_array(T* = NULL) throw();
+ auto_array(auto_array< T >&) throw();
+ auto_array(detail::auto_array_ref< T >) throw();
+ ~auto_array(void) throw();
+
+ T* get(void) throw();
+ const T* get(void) const throw();
+
+ T* release(void) throw();
+ void reset(T* = NULL) throw();
+
+ auto_array< T >& operator=(auto_array< T >&) throw();
+ auto_array< T >& operator=(detail::auto_array_ref< T >) throw();
+ T& operator[](int) throw();
+ const T& operator[](int) const throw();
+ operator detail::auto_array_ref< T >(void) throw();
+};
+
+
+} // namespace utils
+
+
+#endif // !defined(UTILS_AUTO_ARRAY_HPP)
diff --git a/utils/auto_array.ipp b/utils/auto_array.ipp
new file mode 100644
index 000000000000..fd29311def8c
--- /dev/null
+++ b/utils/auto_array.ipp
@@ -0,0 +1,227 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_AUTO_ARRAY_IPP)
+#define UTILS_AUTO_ARRAY_IPP
+
+#include "utils/auto_array.hpp"
+
+namespace utils {
+
+
+namespace detail {
+
+
+/// Constructs a new auto_array_ref from a pointer.
+///
+/// \param ptr The pointer to wrap.
+template< class T > inline
+auto_array_ref< T >::auto_array_ref(T* ptr) :
+ _ptr(ptr)
+{
+}
+
+
+} // namespace detail
+
+
+/// Constructs a new auto_array from a given pointer.
+///
+/// This grabs ownership of the pointer unless it is NULL.
+///
+/// \param ptr The pointer to wrap. If not NULL, the memory pointed to must
+/// have been allocated with operator new[].
+template< class T > inline
+auto_array< T >::auto_array(T* ptr) throw() :
+ _ptr(ptr)
+{
+}
+
+
+/// Constructs a copy of an auto_array.
+///
+/// \param ptr The pointer to copy from. This pointer is invalidated and the
+/// new copy grabs ownership of the object pointed to.
+template< class T > inline
+auto_array< T >::auto_array(auto_array< T >& ptr) throw() :
+ _ptr(ptr.release())
+{
+}
+
+
+/// Constructs a new auto_array form a reference.
+///
+/// Internal function used to construct a new auto_array from an object
+/// returned, for example, from a function.
+///
+/// \param ref The reference.
+template< class T > inline
+auto_array< T >::auto_array(detail::auto_array_ref< T > ref) throw() :
+ _ptr(ref._ptr)
+{
+}
+
+
+/// Destructor for auto_array objects.
+template< class T > inline
+auto_array< T >::~auto_array(void) throw()
+{
+ if (_ptr != NULL)
+ delete [] _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer without releasing ownership.
+///
+/// \return The raw mutable pointer.
+template< class T > inline
+T*
+auto_array< T >::get(void) throw()
+{
+ return _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer without releasing ownership.
+///
+/// \return The raw immutable pointer.
+template< class T > inline
+const T*
+auto_array< T >::get(void) const throw()
+{
+ return _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer and releases ownership.
+///
+/// \return The raw mutable pointer.
+template< class T > inline
+T*
+auto_array< T >::release(void) throw()
+{
+ T* ptr = _ptr;
+ _ptr = NULL;
+ return ptr;
+}
+
+
+/// Changes the value of the wrapped pointer.
+///
+/// If the auto_array was pointing to an array, such array is released and the
+/// wrapped pointer is replaced with the new pointer provided.
+///
+/// \param ptr The pointer to use as a replacement; may be NULL.
+template< class T > inline
+void
+auto_array< T >::reset(T* ptr) throw()
+{
+ if (_ptr != NULL)
+ delete [] _ptr;
+ _ptr = ptr;
+}
+
+
+/// Assignment operator.
+///
+/// \param ptr The object to copy from. This is invalidated after the copy.
+/// \return A reference to the auto_array object itself.
+template< class T > inline
+auto_array< T >&
+auto_array< T >::operator=(auto_array< T >& ptr) throw()
+{
+ reset(ptr.release());
+ return *this;
+}
+
+
+/// Internal assignment operator for function returns.
+///
+/// \param ref The reference object to copy from.
+/// \return A reference to the auto_array object itself.
+template< class T > inline
+auto_array< T >&
+auto_array< T >::operator=(detail::auto_array_ref< T > ref) throw()
+{
+ if (_ptr != ref._ptr) {
+ delete [] _ptr;
+ _ptr = ref._ptr;
+ }
+ return *this;
+}
+
+
+/// Subscript operator to access the array by position.
+///
+/// This does not perform any bounds checking, in particular because auto_array
+/// does not know the size of the arrays pointed to by it.
+///
+/// \param pos The position to access, indexed from zero.
+///
+/// \return A mutable reference to the element at the specified position.
+template< class T > inline
+T&
+auto_array< T >::operator[](int pos) throw()
+{
+ return _ptr[pos];
+}
+
+
+/// Subscript operator to access the array by position.
+///
+/// This does not perform any bounds checking, in particular because auto_array
+/// does not know the size of the arrays pointed to by it.
+///
+/// \param pos The position to access, indexed from zero.
+///
+/// \return An immutable reference to the element at the specified position.
+template< class T > inline
+const T&
+auto_array< T >::operator[](int pos) const throw()
+{
+ return _ptr[pos];
+}
+
+
+/// Internal conversion to a reference wrapper.
+///
+/// This is used internally to support returning auto_array objects from
+/// functions. The auto_array is invalidated when used.
+///
+/// \return A new detail::auto_array_ref object holding the pointer.
+template< class T > inline
+auto_array< T >::operator detail::auto_array_ref< T >(void) throw()
+{
+ return detail::auto_array_ref< T >(release());
+}
+
+
+} // namespace utils
+
+
+#endif // !defined(UTILS_AUTO_ARRAY_IPP)
diff --git a/utils/auto_array_fwd.hpp b/utils/auto_array_fwd.hpp
new file mode 100644
index 000000000000..e1522a25bf7d
--- /dev/null
+++ b/utils/auto_array_fwd.hpp
@@ -0,0 +1,43 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/auto_array_fwd.hpp
+/// Forward declarations for utils/auto_array.hpp
+
+#if !defined(UTILS_AUTO_ARRAY_FWD_HPP)
+#define UTILS_AUTO_ARRAY_FWD_HPP
+
+namespace utils {
+
+
+template< class > class auto_array;
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_AUTO_ARRAY_FWD_HPP)
diff --git a/utils/auto_array_test.cpp b/utils/auto_array_test.cpp
new file mode 100644
index 000000000000..041eb65863ba
--- /dev/null
+++ b/utils/auto_array_test.cpp
@@ -0,0 +1,312 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/auto_array.ipp"
+
+extern "C" {
+#include <sys/types.h>
+}
+
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+
+using utils::auto_array;
+
+
+namespace {
+
+
+/// Mock class to capture calls to the new and delete operators.
+class test_array {
+public:
+ /// User-settable cookie to disambiguate instances of this class.
+ int m_value;
+
+ /// The current balance of existing test_array instances.
+ static ssize_t m_nblocks;
+
+ /// Captures invalid calls to new on an array.
+ ///
+ /// \return Nothing; this always fails the test case.
+ void*
+ operator new(const size_t /* size */)
+ {
+ ATF_FAIL("New called but should have been new[]");
+ return new int(5);
+ }
+
+ /// Obtains memory for a new instance and increments m_nblocks.
+ ///
+ /// \param size The amount of memory to allocate, in bytes.
+ ///
+ /// \return A pointer to the allocated memory.
+ ///
+ /// \throw std::bad_alloc If the memory cannot be allocated.
+ void*
+ operator new[](const size_t size)
+ {
+ void* mem = ::operator new(size);
+ m_nblocks++;
+ std::cout << "Allocated 'test_array' object " << mem << "\n";
+ return mem;
+ }
+
+ /// Captures invalid calls to delete on an array.
+ ///
+ /// \return Nothing; this always fails the test case.
+ void
+ operator delete(void* /* mem */)
+ {
+ ATF_FAIL("Delete called but should have been delete[]");
+ }
+
+ /// Deletes a previously allocated array and decrements m_nblocks.
+ ///
+ /// \param mem The pointer to the memory to be deleted.
+ void
+ operator delete[](void* mem)
+ {
+ std::cout << "Releasing 'test_array' object " << mem << "\n";
+ if (m_nblocks == 0)
+ ATF_FAIL("Unbalanced delete[]");
+ m_nblocks--;
+ ::operator delete(mem);
+ }
+};
+
+
+ssize_t test_array::m_nblocks = 0;
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE(scope);
+ATF_TEST_CASE_HEAD(scope)
+{
+ set_md_var("descr", "Tests the automatic scope handling in the "
+ "auto_array smart pointer class");
+}
+ATF_TEST_CASE_BODY(scope)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(copy);
+ATF_TEST_CASE_HEAD(copy)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor");
+}
+ATF_TEST_CASE_BODY(copy)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2(t1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(copy_ref);
+ATF_TEST_CASE_HEAD(copy_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor through the auxiliary ref object");
+}
+ATF_TEST_CASE_BODY(copy_ref)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(get);
+ATF_TEST_CASE_HEAD(get)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' get "
+ "method");
+}
+ATF_TEST_CASE_BODY(get)
+{
+ test_array* ta = new test_array[10];
+ auto_array< test_array > t(ta);
+ ATF_REQUIRE_EQ(t.get(), ta);
+}
+
+
+ATF_TEST_CASE(release);
+ATF_TEST_CASE_HEAD(release)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' release "
+ "method");
+}
+ATF_TEST_CASE_BODY(release)
+{
+ test_array* ta1 = new test_array[10];
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ test_array* ta2 = t.release();
+ ATF_REQUIRE_EQ(ta2, ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ delete [] ta1;
+}
+
+
+ATF_TEST_CASE(reset);
+ATF_TEST_CASE_HEAD(reset)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' reset "
+ "method");
+}
+ATF_TEST_CASE_BODY(reset)
+{
+ test_array* ta1 = new test_array[10];
+ test_array* ta2 = new test_array[10];
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+ t.reset(ta2);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ t.reset();
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(assign);
+ATF_TEST_CASE_HEAD(assign)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator");
+}
+ATF_TEST_CASE_BODY(assign)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(assign_ref);
+ATF_TEST_CASE_HEAD(assign_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator through the auxiliary ref "
+ "object");
+}
+ATF_TEST_CASE_BODY(assign_ref)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(access);
+ATF_TEST_CASE_HEAD(access)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' access "
+ "operator");
+}
+ATF_TEST_CASE_BODY(access)
+{
+ auto_array< test_array > t(new test_array[10]);
+
+ for (int i = 0; i < 10; i++)
+ t[i].m_value = i * 2;
+
+ for (int i = 0; i < 10; i++)
+ ATF_REQUIRE_EQ(t[i].m_value, i * 2);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, scope);
+ ATF_ADD_TEST_CASE(tcs, copy);
+ ATF_ADD_TEST_CASE(tcs, copy_ref);
+ ATF_ADD_TEST_CASE(tcs, get);
+ ATF_ADD_TEST_CASE(tcs, release);
+ ATF_ADD_TEST_CASE(tcs, reset);
+ ATF_ADD_TEST_CASE(tcs, assign);
+ ATF_ADD_TEST_CASE(tcs, assign_ref);
+ ATF_ADD_TEST_CASE(tcs, access);
+}
diff --git a/utils/cmdline/Kyuafile b/utils/cmdline/Kyuafile
new file mode 100644
index 000000000000..d5e6f7122b07
--- /dev/null
+++ b/utils/cmdline/Kyuafile
@@ -0,0 +1,11 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="base_command_test"}
+atf_test_program{name="commands_map_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="globals_test"}
+atf_test_program{name="options_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="ui_test"}
diff --git a/utils/cmdline/Makefile.am.inc b/utils/cmdline/Makefile.am.inc
new file mode 100644
index 000000000000..65081cbeafee
--- /dev/null
+++ b/utils/cmdline/Makefile.am.inc
@@ -0,0 +1,96 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/cmdline/base_command.cpp
+libutils_a_SOURCES += utils/cmdline/base_command.hpp
+libutils_a_SOURCES += utils/cmdline/base_command_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/base_command.ipp
+libutils_a_SOURCES += utils/cmdline/commands_map.hpp
+libutils_a_SOURCES += utils/cmdline/commands_map_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/commands_map.ipp
+libutils_a_SOURCES += utils/cmdline/exceptions.cpp
+libutils_a_SOURCES += utils/cmdline/exceptions.hpp
+libutils_a_SOURCES += utils/cmdline/globals.cpp
+libutils_a_SOURCES += utils/cmdline/globals.hpp
+libutils_a_SOURCES += utils/cmdline/options.cpp
+libutils_a_SOURCES += utils/cmdline/options.hpp
+libutils_a_SOURCES += utils/cmdline/options_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/parser.cpp
+libutils_a_SOURCES += utils/cmdline/parser.hpp
+libutils_a_SOURCES += utils/cmdline/parser_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/parser.ipp
+libutils_a_SOURCES += utils/cmdline/ui.cpp
+libutils_a_SOURCES += utils/cmdline/ui.hpp
+libutils_a_SOURCES += utils/cmdline/ui_fwd.hpp
+# The following two files are only supposed to be used from test code. They
+# should not be bundled into libutils.a, but doing so simplifies the build
+# significantly.
+libutils_a_SOURCES += utils/cmdline/ui_mock.hpp
+libutils_a_SOURCES += utils/cmdline/ui_mock.cpp
+
+if WITH_ATF
+tests_utils_cmdlinedir = $(pkgtestsdir)/utils/cmdline
+
+tests_utils_cmdline_DATA = utils/cmdline/Kyuafile
+EXTRA_DIST += $(tests_utils_cmdline_DATA)
+
+tests_utils_cmdline_PROGRAMS = utils/cmdline/base_command_test
+utils_cmdline_base_command_test_SOURCES = utils/cmdline/base_command_test.cpp
+utils_cmdline_base_command_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_base_command_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/commands_map_test
+utils_cmdline_commands_map_test_SOURCES = utils/cmdline/commands_map_test.cpp
+utils_cmdline_commands_map_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_commands_map_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/exceptions_test
+utils_cmdline_exceptions_test_SOURCES = utils/cmdline/exceptions_test.cpp
+utils_cmdline_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/globals_test
+utils_cmdline_globals_test_SOURCES = utils/cmdline/globals_test.cpp
+utils_cmdline_globals_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_globals_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/options_test
+utils_cmdline_options_test_SOURCES = utils/cmdline/options_test.cpp
+utils_cmdline_options_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_options_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/parser_test
+utils_cmdline_parser_test_SOURCES = utils/cmdline/parser_test.cpp
+utils_cmdline_parser_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_parser_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/ui_test
+utils_cmdline_ui_test_SOURCES = utils/cmdline/ui_test.cpp
+utils_cmdline_ui_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_ui_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/cmdline/base_command.cpp b/utils/cmdline/base_command.cpp
new file mode 100644
index 000000000000..837ded9cffab
--- /dev/null
+++ b/utils/cmdline/base_command.cpp
@@ -0,0 +1,201 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/base_command.hpp"
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/cmdline/parser.ipp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+cmdline::command_proto::command_proto(const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ _name(name_),
+ _arg_list(arg_list_),
+ _min_args(min_args_),
+ _max_args(max_args_),
+ _short_description(short_description_)
+{
+ PRE(name_.find(' ') == std::string::npos);
+ PRE(max_args_ == -1 || min_args_ <= max_args_);
+}
+
+
+/// Destructor for a command.
+cmdline::command_proto::~command_proto(void)
+{
+ for (options_vector::const_iterator iter = _options.begin();
+ iter != _options.end(); iter++)
+ delete *iter;
+}
+
+
+/// Internal method to register a dynamically-allocated option.
+///
+/// Always use add_option() from subclasses to add options.
+///
+/// \param option_ The option to add. Must have been dynamically allocated.
+/// This grabs ownership of the pointer, which is released when the command
+/// is destroyed.
+void
+cmdline::command_proto::add_option_ptr(const cmdline::base_option* option_)
+{
+ try {
+ _options.push_back(option_);
+ } catch (...) {
+ delete option_;
+ throw;
+ }
+}
+
+
+/// Processes the command line based on the command description.
+///
+/// \param args The raw command line to be processed.
+///
+/// \return An object containing the list of options and free arguments found in
+/// args.
+///
+/// \throw cmdline::usage_error If there is a problem processing the command
+/// line. This error is caused by invalid input from the user.
+cmdline::parsed_cmdline
+cmdline::command_proto::parse_cmdline(const cmdline::args_vector& args) const
+{
+ PRE(name() == args[0]);
+ const parsed_cmdline cmdline = cmdline::parse(args, options());
+
+ const int argc = cmdline.arguments().size();
+ if (argc < _min_args)
+ throw usage_error("Not enough arguments");
+ if (_max_args != -1 && argc > _max_args)
+ throw usage_error("Too many arguments");
+
+ return cmdline;
+}
+
+
+/// Gets the name of the command.
+///
+/// \return The command name.
+const std::string&
+cmdline::command_proto::name(void) const
+{
+ return _name;
+}
+
+
+/// Gets the textual representation of the arguments list.
+///
+/// \return The description of the arguments list.
+const std::string&
+cmdline::command_proto::arg_list(void) const
+{
+ return _arg_list;
+}
+
+
+/// Gets the description of the purpose of the command.
+///
+/// \return The description of the command.
+const std::string&
+cmdline::command_proto::short_description(void) const
+{
+ return _short_description;
+}
+
+
+/// Gets the definition of the options accepted by the command.
+///
+/// \return The list of options.
+const cmdline::options_vector&
+cmdline::command_proto::options(void) const
+{
+ return _options;
+}
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+cmdline::base_command_no_data::base_command_no_data(
+ const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ command_proto(name_, arg_list_, min_args_, max_args_, short_description_)
+{
+}
+
+
+/// Entry point for the command.
+///
+/// This delegates execution to the run() abstract function after the command
+/// line provided in args has been parsed.
+///
+/// If this function returns, the command is assumed to have been executed
+/// successfully. Any error must be reported by means of exceptions.
+///
+/// \param ui Object to interact with the I/O of the command. The command must
+/// always use this object to write to stdout and stderr.
+/// \param args The command line passed to the command broken by word, which
+/// includes options and arguments.
+///
+/// \return The exit code that the program has to return. 0 on success, some
+/// other value on error.
+/// \throw usage_error If args is invalid (i.e. if the options are mispecified
+/// or if the arguments are invalid).
+int
+cmdline::base_command_no_data::main(cmdline::ui* ui,
+ const cmdline::args_vector& args)
+{
+ return run(ui, parse_cmdline(args));
+}
diff --git a/utils/cmdline/base_command.hpp b/utils/cmdline/base_command.hpp
new file mode 100644
index 000000000000..819dfe98dad3
--- /dev/null
+++ b/utils/cmdline/base_command.hpp
@@ -0,0 +1,162 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/base_command.hpp
+/// Provides the utils::cmdline::base_command class.
+
+#if !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
+#define UTILS_CMDLINE_BASE_COMMAND_HPP
+
+#include "utils/cmdline/base_command_fwd.hpp"
+
+#include <string>
+
+#include "utils/cmdline/options_fwd.hpp"
+#include "utils/cmdline/parser_fwd.hpp"
+#include "utils/cmdline/ui_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Prototype class for the implementation of subcommands of a program.
+///
+/// Use the subclasses of command_proto defined in this module instead of
+/// command_proto itself as base classes for your application-specific
+/// commands.
+class command_proto : noncopyable {
+ /// The user-visible name of the command.
+ const std::string _name;
+
+ /// Textual description of the command arguments.
+ const std::string _arg_list;
+
+ /// The minimum number of required arguments.
+ const int _min_args;
+
+ /// The maximum number of allowed arguments; -1 for infinity.
+ const int _max_args;
+
+ /// A textual description of the command.
+ const std::string _short_description;
+
+ /// Collection of command-specific options.
+ options_vector _options;
+
+ void add_option_ptr(const base_option*);
+
+protected:
+ template< typename Option > void add_option(const Option&);
+ parsed_cmdline parse_cmdline(const args_vector&) const;
+
+public:
+ command_proto(const std::string&, const std::string&, const int, const int,
+ const std::string&);
+ virtual ~command_proto(void);
+
+ const std::string& name(void) const;
+ const std::string& arg_list(void) const;
+ const std::string& short_description(void) const;
+ const options_vector& options(void) const;
+};
+
+
+/// Unparametrized base subcommand for a program.
+///
+/// Use this class to define subcommands for your program that do not need any
+/// information passed in from the main command-line dispatcher other than the
+/// command-line arguments.
+class base_command_no_data : public command_proto {
+ /// Main code of the command.
+ ///
+ /// This is called from main() after the command line has been processed and
+ /// validated.
+ ///
+ /// \param ui Object to interact with the I/O of the command. The command
+ /// must always use this object to write to stdout and stderr.
+ /// \param cmdline The parsed command line, containing the values of any
+ /// given options and arguments.
+ ///
+ /// \return The exit code that the program has to return. 0 on success,
+ /// some other value on error.
+ ///
+ /// \throw std::runtime_error Any errors detected during the execution of
+ /// the command are reported by means of exceptions.
+ virtual int run(ui* ui, const parsed_cmdline& cmdline) = 0;
+
+public:
+ base_command_no_data(const std::string&, const std::string&, const int,
+ const int, const std::string&);
+
+ int main(ui*, const args_vector&);
+};
+
+
+/// Parametrized base subcommand for a program.
+///
+/// Use this class to define subcommands for your program that need some kind of
+/// runtime information passed in from the main command-line dispatcher.
+///
+/// \param Data The type of the object passed to the subcommand at runtime.
+/// This is useful, for example, to pass around the runtime configuration of the
+/// program.
+template< typename Data >
+class base_command : public command_proto {
+ /// Main code of the command.
+ ///
+ /// This is called from main() after the command line has been processed and
+ /// validated.
+ ///
+ /// \param ui Object to interact with the I/O of the command. The command
+ /// must always use this object to write to stdout and stderr.
+ /// \param cmdline The parsed command line, containing the values of any
+ /// given options and arguments.
+ /// \param data An instance of the runtime data passed from main().
+ ///
+ /// \return The exit code that the program has to return. 0 on success,
+ /// some other value on error.
+ ///
+ /// \throw std::runtime_error Any errors detected during the execution of
+ /// the command are reported by means of exceptions.
+ virtual int run(ui* ui, const parsed_cmdline& cmdline,
+ const Data& data) = 0;
+
+public:
+ base_command(const std::string&, const std::string&, const int, const int,
+ const std::string&);
+
+ int main(ui*, const args_vector&, const Data&);
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
diff --git a/utils/cmdline/base_command.ipp b/utils/cmdline/base_command.ipp
new file mode 100644
index 000000000000..5696637085d7
--- /dev/null
+++ b/utils/cmdline/base_command.ipp
@@ -0,0 +1,104 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_CMDLINE_BASE_COMMAND_IPP)
+#define UTILS_CMDLINE_BASE_COMMAND_IPP
+
+#include "utils/cmdline/base_command.hpp"
+
+
+namespace utils {
+namespace cmdline {
+
+
+/// Adds an option to the command.
+///
+/// This is to be called from the constructor of the subclass that implements
+/// the command.
+///
+/// \param option_ The option to add.
+template< typename Option >
+void
+command_proto::add_option(const Option& option_)
+{
+ add_option_ptr(new Option(option_));
+}
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+template< typename Data >
+base_command< Data >::base_command(const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ command_proto(name_, arg_list_, min_args_, max_args_, short_description_)
+{
+}
+
+
+/// Entry point for the command.
+///
+/// This delegates execution to the run() abstract function after the command
+/// line provided in args has been parsed.
+///
+/// If this function returns, the command is assumed to have been executed
+/// successfully. Any error must be reported by means of exceptions.
+///
+/// \param ui Object to interact with the I/O of the command. The command must
+/// always use this object to write to stdout and stderr.
+/// \param args The command line passed to the command broken by word, which
+/// includes options and arguments.
+/// \param data An opaque data structure to pass to the run method.
+///
+/// \return The exit code that the program has to return. 0 on success, some
+/// other value on error.
+/// \throw usage_error If args is invalid (i.e. if the options are mispecified
+/// or if the arguments are invalid).
+template< typename Data >
+int
+base_command< Data >::main(ui* ui, const args_vector& args, const Data& data)
+{
+ return run(ui, parse_cmdline(args), data);
+}
+
+
+} // namespace cli
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_IPP)
diff --git a/utils/cmdline/base_command_fwd.hpp b/utils/cmdline/base_command_fwd.hpp
new file mode 100644
index 000000000000..c94db1ae2d05
--- /dev/null
+++ b/utils/cmdline/base_command_fwd.hpp
@@ -0,0 +1,47 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/base_command_fwd.hpp
+/// Forward declarations for utils/cmdline/base_command.hpp
+
+#if !defined(UTILS_CMDLINE_BASE_COMMAND_FWD_HPP)
+#define UTILS_CMDLINE_BASE_COMMAND_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class command_proto;
+class base_command_no_data;
+template< typename > class base_command;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_FWD_HPP)
diff --git a/utils/cmdline/base_command_test.cpp b/utils/cmdline/base_command_test.cpp
new file mode 100644
index 000000000000..20df8ea49512
--- /dev/null
+++ b/utils/cmdline/base_command_test.cpp
@@ -0,0 +1,295 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/base_command.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/cmdline/parser.ipp"
+#include "utils/cmdline/ui_mock.hpp"
+#include "utils/defs.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+namespace {
+
+
+/// Mock command to test the cmdline::base_command base class.
+///
+/// \param Data The type of the opaque data object passed to main().
+/// \param ExpectedData The value run() will expect to find in the Data object
+/// passed to main().
+template< typename Data, Data ExpectedData >
+class mock_cmd : public cmdline::base_command< Data > {
+public:
+ /// Indicates if run() has been called already and executed correctly.
+ bool executed;
+
+ /// Contains the argument of --the_string after run() is executed.
+ std::string optvalue;
+
+ /// Constructs a new mock command.
+ mock_cmd(void) :
+ cmdline::base_command< Data >("mock", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing."),
+ executed(false)
+ {
+ this->add_option(cmdline::string_option("the_string", "Test option",
+ "arg"));
+ }
+
+ /// Executes the command.
+ ///
+ /// \param cmdline Representation of the command line to the subcommand.
+ /// \param data Arbitrary data cookie passed to the command.
+ ///
+ /// \return A hardcoded number for testing purposes.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& cmdline, const Data& data)
+ {
+ if (cmdline.has_option("the_string"))
+ optvalue = cmdline.get_option< cmdline::string_option >(
+ "the_string");
+ ATF_REQUIRE_EQ(ExpectedData, data);
+ executed = true;
+ return 1234;
+ }
+};
+
+
+/// Mock command to test the cmdline::base_command_no_data base class.
+class mock_cmd_no_data : public cmdline::base_command_no_data {
+public:
+ /// Indicates if run() has been called already and executed correctly.
+ bool executed;
+
+ /// Contains the argument of --the_string after run() is executed.
+ std::string optvalue;
+
+ /// Constructs a new mock command.
+ mock_cmd_no_data(void) :
+ cmdline::base_command_no_data("mock", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing."),
+ executed(false)
+ {
+ add_option(cmdline::string_option("the_string", "Test option", "arg"));
+ }
+
+ /// Executes the command.
+ ///
+ /// \param cmdline Representation of the command line to the subcommand.
+ ///
+ /// \return A hardcoded number for testing purposes.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& cmdline)
+ {
+ if (cmdline.has_option("the_string"))
+ optvalue = cmdline.get_option< cmdline::string_option >(
+ "the_string");
+ executed = true;
+ return 1234;
+ }
+};
+
+
+/// Implementation of a command to get access to parse_cmdline().
+class parse_cmdline_portal : public cmdline::command_proto {
+public:
+ /// Constructs a new mock command.
+ parse_cmdline_portal(void) :
+ cmdline::command_proto("portal", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing.")
+ {
+ this->add_option(cmdline::string_option("the_string", "Test option",
+ "arg"));
+ }
+
+ /// Delegator for the internal parse_cmdline() method.
+ ///
+ /// \param args The input arguments to be parsed.
+ ///
+ /// \return The parsed command line, split in options and arguments.
+ cmdline::parsed_cmdline
+ operator()(const cmdline::args_vector& args) const
+ {
+ return parse_cmdline(args);
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__ok);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__ok)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ (void)parse_cmdline_portal()(args);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__parse_fail);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__parse_fail)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ (void)parse_cmdline_portal()(args));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__args_invalid);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__args_invalid)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Not enough arguments",
+ (void)parse_cmdline_portal()(args));
+
+ args.push_back("1");
+ args.push_back("2");
+ args.push_back("3");
+ args.push_back("4");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Too many arguments",
+ (void)parse_cmdline_portal()(args));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__getters);
+ATF_TEST_CASE_BODY(base_command__getters)
+{
+ mock_cmd< int, 584 > cmd;
+ ATF_REQUIRE_EQ("mock", cmd.name());
+ ATF_REQUIRE_EQ("arg1 [arg2 [arg3]]", cmd.arg_list());
+ ATF_REQUIRE_EQ("Command for testing.", cmd.short_description());
+ ATF_REQUIRE_EQ(1, cmd.options().size());
+ ATF_REQUIRE_EQ("the_string", cmd.options()[0]->long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__main__ok)
+ATF_TEST_CASE_BODY(base_command__main__ok)
+{
+ mock_cmd< int, 584 > cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ ATF_REQUIRE_EQ(1234, cmd.main(&ui, args, 584));
+ ATF_REQUIRE(cmd.executed);
+ ATF_REQUIRE_EQ("foo bar", cmd.optvalue);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__main__parse_cmdline_fail)
+ATF_TEST_CASE_BODY(base_command__main__parse_cmdline_fail)
+{
+ mock_cmd< int, 584 > cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ cmd.main(&ui, args, 584));
+ ATF_REQUIRE(!cmd.executed);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__getters);
+ATF_TEST_CASE_BODY(base_command_no_data__getters)
+{
+ mock_cmd_no_data cmd;
+ ATF_REQUIRE_EQ("mock", cmd.name());
+ ATF_REQUIRE_EQ("arg1 [arg2 [arg3]]", cmd.arg_list());
+ ATF_REQUIRE_EQ("Command for testing.", cmd.short_description());
+ ATF_REQUIRE_EQ(1, cmd.options().size());
+ ATF_REQUIRE_EQ("the_string", cmd.options()[0]->long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__main__ok)
+ATF_TEST_CASE_BODY(base_command_no_data__main__ok)
+{
+ mock_cmd_no_data cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ ATF_REQUIRE_EQ(1234, cmd.main(&ui, args));
+ ATF_REQUIRE(cmd.executed);
+ ATF_REQUIRE_EQ("foo bar", cmd.optvalue);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__main__parse_cmdline_fail)
+ATF_TEST_CASE_BODY(base_command_no_data__main__parse_cmdline_fail)
+{
+ mock_cmd_no_data cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ cmd.main(&ui, args));
+ ATF_REQUIRE(!cmd.executed);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__ok);
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__parse_fail);
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__args_invalid);
+
+ ATF_ADD_TEST_CASE(tcs, base_command__getters);
+ ATF_ADD_TEST_CASE(tcs, base_command__main__ok);
+ ATF_ADD_TEST_CASE(tcs, base_command__main__parse_cmdline_fail);
+
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__getters);
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__main__ok);
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__main__parse_cmdline_fail);
+}
diff --git a/utils/cmdline/commands_map.hpp b/utils/cmdline/commands_map.hpp
new file mode 100644
index 000000000000..5378a6f2c471
--- /dev/null
+++ b/utils/cmdline/commands_map.hpp
@@ -0,0 +1,96 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/commands_map.hpp
+/// Maintains a collection of dynamically-instantiated commands.
+///
+/// Commands need to be dynamically-instantiated because they are often
+/// complex data structures. Instantiating them as static variables causes
+/// problems with the order of construction of globals. The commands_map class
+/// provided by this module provides a mechanism to maintain these instantiated
+/// objects.
+
+#if !defined(UTILS_CMDLINE_COMMANDS_MAP_HPP)
+#define UTILS_CMDLINE_COMMANDS_MAP_HPP
+
+#include "utils/cmdline/commands_map_fwd.hpp"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "utils/noncopyable.hpp"
+
+
+namespace utils {
+namespace cmdline {
+
+
+/// Collection of dynamically-instantiated commands.
+template< typename BaseCommand >
+class commands_map : noncopyable {
+ /// Map of command names to their implementations.
+ typedef std::map< std::string, BaseCommand* > impl_map;
+
+ /// Map of category names to the command names they contain.
+ typedef std::map< std::string, std::set< std::string > > categories_map;
+
+ /// Collection of all available commands.
+ impl_map _commands;
+
+ /// Collection of defined categories and their commands.
+ categories_map _categories;
+
+public:
+ commands_map(void);
+ ~commands_map(void);
+
+ /// Scoped, strictly-owned pointer to a command from this map.
+ typedef typename std::auto_ptr< BaseCommand > command_ptr;
+ void insert(command_ptr, const std::string& = "");
+ void insert(BaseCommand*, const std::string& = "");
+
+ /// Type for a constant iterator.
+ typedef typename categories_map::const_iterator const_iterator;
+
+ bool empty(void) const;
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+
+ BaseCommand* find(const std::string&);
+ const BaseCommand* find(const std::string&) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
diff --git a/utils/cmdline/commands_map.ipp b/utils/cmdline/commands_map.ipp
new file mode 100644
index 000000000000..8be87ab3b5cc
--- /dev/null
+++ b/utils/cmdline/commands_map.ipp
@@ -0,0 +1,161 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/commands_map.hpp"
+#include "utils/sanity.hpp"
+
+
+namespace utils {
+
+
+/// Constructs an empty set of commands.
+template< typename BaseCommand >
+cmdline::commands_map< BaseCommand >::commands_map(void)
+{
+}
+
+
+/// Destroys a set of commands.
+///
+/// This releases the dynamically-instantiated objects.
+template< typename BaseCommand >
+cmdline::commands_map< BaseCommand >::~commands_map(void)
+{
+ for (typename impl_map::iterator iter = _commands.begin();
+ iter != _commands.end(); iter++)
+ delete (*iter).second;
+}
+
+
+/// Inserts a new command into the map.
+///
+/// \param command The command to insert. This must have been dynamically
+/// allocated with new. The call grabs ownership of the command, or the
+/// command is freed if the call fails.
+/// \param category The category this command belongs to. Defaults to the empty
+/// string, which indicates that the command has not be categorized.
+template< typename BaseCommand >
+void
+cmdline::commands_map< BaseCommand >::insert(command_ptr command,
+ const std::string& category)
+{
+ INV(_commands.find(command->name()) == _commands.end());
+ BaseCommand* ptr = command.release();
+ INV(ptr != NULL);
+ _commands[ptr->name()] = ptr;
+ _categories[category].insert(ptr->name());
+}
+
+
+/// Inserts a new command into the map.
+///
+/// This grabs ownership of the pointer, so it is ONLY safe to use with the
+/// following idiom: insert(new foo()).
+///
+/// \param command The command to insert. This must have been dynamically
+/// allocated with new. The call grabs ownership of the command, or the
+/// command is freed if the call fails.
+/// \param category The category this command belongs to. Defaults to the empty
+/// string, which indicates that the command has not be categorized.
+template< typename BaseCommand >
+void
+cmdline::commands_map< BaseCommand >::insert(BaseCommand* command,
+ const std::string& category)
+{
+ insert(command_ptr(command), category);
+}
+
+
+/// Checks whether the list of commands is empty.
+///
+/// \return True if there are no commands in this map.
+template< typename BaseCommand >
+bool
+cmdline::commands_map< BaseCommand >::empty(void) const
+{
+ return _commands.empty();
+}
+
+
+/// Returns a constant iterator to the beginning of the categories mapping.
+///
+/// \return A map (string -> BaseCommand*) iterator.
+template< typename BaseCommand >
+typename cmdline::commands_map< BaseCommand >::const_iterator
+cmdline::commands_map< BaseCommand >::begin(void) const
+{
+ return _categories.begin();
+}
+
+
+/// Returns a constant iterator to the end of the categories mapping.
+///
+/// \return A map (string -> BaseCommand*) iterator.
+template< typename BaseCommand >
+typename cmdline::commands_map< BaseCommand >::const_iterator
+cmdline::commands_map< BaseCommand >::end(void) const
+{
+ return _categories.end();
+}
+
+
+/// Finds a command by name; mutable version.
+///
+/// \param name The name of the command to locate.
+///
+/// \return The command itself or NULL if it does not exist.
+template< typename BaseCommand >
+BaseCommand*
+cmdline::commands_map< BaseCommand >::find(const std::string& name)
+{
+ typename impl_map::iterator iter = _commands.find(name);
+ if (iter == _commands.end())
+ return NULL;
+ else
+ return (*iter).second;
+}
+
+
+/// Finds a command by name; constant version.
+///
+/// \param name The name of the command to locate.
+///
+/// \return The command itself or NULL if it does not exist.
+template< typename BaseCommand >
+const BaseCommand*
+cmdline::commands_map< BaseCommand >::find(const std::string& name) const
+{
+ typename impl_map::const_iterator iter = _commands.find(name);
+ if (iter == _commands.end())
+ return NULL;
+ else
+ return (*iter).second;
+}
+
+
+} // namespace utils
diff --git a/utils/cmdline/commands_map_fwd.hpp b/utils/cmdline/commands_map_fwd.hpp
new file mode 100644
index 000000000000..a81a852790da
--- /dev/null
+++ b/utils/cmdline/commands_map_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/commands_map_fwd.hpp
+/// Forward declarations for utils/cmdline/commands_map.hpp
+
+#if !defined(UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP)
+#define UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+template< typename > class commands_map;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP)
diff --git a/utils/cmdline/commands_map_test.cpp b/utils/cmdline/commands_map_test.cpp
new file mode 100644
index 000000000000..47a7404f64fb
--- /dev/null
+++ b/utils/cmdline/commands_map_test.cpp
@@ -0,0 +1,140 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/commands_map.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/base_command.hpp"
+#include "utils/defs.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+namespace {
+
+
+/// Fake command to validate the behavior of commands_map.
+///
+/// Note that this command does not do anything. It is only intended to provide
+/// a specific class that can be inserted into commands_map instances and check
+/// that it can be located properly.
+class mock_cmd : public cmdline::base_command_no_data {
+public:
+ /// Constructor for the mock command.
+ ///
+ /// \param mock_name The name of the command. All other settings are set to
+ /// irrelevant values.
+ mock_cmd(const char* mock_name) :
+ cmdline::base_command_no_data(mock_name, "", 0, 0,
+ "Command for testing.")
+ {
+ }
+
+ /// Runs the mock command.
+ ///
+ /// \return Nothing because this function is never called.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& /* cmdline */)
+ {
+ UNREACHABLE;
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(empty);
+ATF_TEST_CASE_BODY(empty)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ ATF_REQUIRE(commands.empty());
+ ATF_REQUIRE(commands.begin() == commands.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some);
+ATF_TEST_CASE_BODY(some)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ cmdline::base_command_no_data* cmd1 = new mock_cmd("cmd1");
+ commands.insert(cmd1);
+ cmdline::base_command_no_data* cmd2 = new mock_cmd("cmd2");
+ commands.insert(cmd2, "foo");
+
+ ATF_REQUIRE(!commands.empty());
+
+ cmdline::commands_map< cmdline::base_command_no_data >::const_iterator
+ iter = commands.begin();
+ ATF_REQUIRE_EQ("", (*iter).first);
+ ATF_REQUIRE_EQ(1, (*iter).second.size());
+ ATF_REQUIRE_EQ("cmd1", *(*iter).second.begin());
+
+ ++iter;
+ ATF_REQUIRE_EQ("foo", (*iter).first);
+ ATF_REQUIRE_EQ(1, (*iter).second.size());
+ ATF_REQUIRE_EQ("cmd2", *(*iter).second.begin());
+
+ ATF_REQUIRE(++iter == commands.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find__match);
+ATF_TEST_CASE_BODY(find__match)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ cmdline::base_command_no_data* cmd1 = new mock_cmd("cmd1");
+ commands.insert(cmd1);
+ cmdline::base_command_no_data* cmd2 = new mock_cmd("cmd2");
+ commands.insert(cmd2);
+
+ ATF_REQUIRE(cmd1 == commands.find("cmd1"));
+ ATF_REQUIRE(cmd2 == commands.find("cmd2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find__nomatch);
+ATF_TEST_CASE_BODY(find__nomatch)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ commands.insert(new mock_cmd("cmd1"));
+
+ ATF_REQUIRE(NULL == commands.find("cmd2"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, empty);
+ ATF_ADD_TEST_CASE(tcs, some);
+ ATF_ADD_TEST_CASE(tcs, find__match);
+ ATF_ADD_TEST_CASE(tcs, find__nomatch);
+}
diff --git a/utils/cmdline/exceptions.cpp b/utils/cmdline/exceptions.cpp
new file mode 100644
index 000000000000..fa9ba2218a7f
--- /dev/null
+++ b/utils/cmdline/exceptions.cpp
@@ -0,0 +1,175 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/exceptions.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+#define VALIDATE_OPTION_NAME(option) PRE_MSG( \
+ (option.length() == 2 && (option[0] == '-' && option[1] != '-')) || \
+ (option.length() > 2 && (option[0] == '-' && option[1] == '-')), \
+ F("The option name %s must be fully specified") % option);
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+cmdline::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+cmdline::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new usage_error.
+///
+/// \param message The reason behind the usage error.
+cmdline::usage_error::usage_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+cmdline::usage_error::~usage_error(void) throw()
+{
+}
+
+
+/// Constructs a new missing_option_argument_error.
+///
+/// \param option_ The option for which no argument was provided. The option
+/// name must be fully specified (with - or -- in front).
+cmdline::missing_option_argument_error::missing_option_argument_error(
+ const std::string& option_) :
+ usage_error(F("Missing required argument for option %s") % option_),
+ _option(option_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::missing_option_argument_error::~missing_option_argument_error(void)
+ throw()
+{
+}
+
+
+/// Returns the option name for which no argument was provided.
+///
+/// \return The option name.
+const std::string&
+cmdline::missing_option_argument_error::option(void) const
+{
+ return _option;
+}
+
+
+/// Constructs a new option_argument_value_error.
+///
+/// \param option_ The option to which an invalid argument was passed. The
+/// option name must be fully specified (with - or -- in front).
+/// \param argument_ The invalid argument.
+/// \param reason_ The reason describing why the argument is invalid.
+cmdline::option_argument_value_error::option_argument_value_error(
+ const std::string& option_, const std::string& argument_,
+ const std::string& reason_) :
+ usage_error(F("Invalid argument '%s' for option %s: %s") % argument_ %
+ option_ % reason_),
+ _option(option_),
+ _argument(argument_),
+ _reason(reason_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::option_argument_value_error::~option_argument_value_error(void)
+ throw()
+{
+}
+
+
+/// Returns the option to which the invalid argument was passed.
+///
+/// \return The option name.
+const std::string&
+cmdline::option_argument_value_error::option(void) const
+{
+ return _option;
+}
+
+
+/// Returns the invalid argument value.
+///
+/// \return The invalid argument.
+const std::string&
+cmdline::option_argument_value_error::argument(void) const
+{
+ return _argument;
+}
+
+
+/// Constructs a new unknown_option_error.
+///
+/// \param option_ The unknown option. The option name must be fully specified
+/// (with - or -- in front).
+cmdline::unknown_option_error::unknown_option_error(
+ const std::string& option_) :
+ usage_error(F("Unknown option %s") % option_),
+ _option(option_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::unknown_option_error::~unknown_option_error(void) throw()
+{
+}
+
+
+/// Returns the unknown option name.
+///
+/// \return The unknown option.
+const std::string&
+cmdline::unknown_option_error::option(void) const
+{
+ return _option;
+}
diff --git a/utils/cmdline/exceptions.hpp b/utils/cmdline/exceptions.hpp
new file mode 100644
index 000000000000..59f99e835ce1
--- /dev/null
+++ b/utils/cmdline/exceptions.hpp
@@ -0,0 +1,109 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/exceptions.hpp
+/// Exception types raised by the cmdline module.
+
+#if !defined(UTILS_CMDLINE_EXCEPTIONS_HPP)
+#define UTILS_CMDLINE_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace utils {
+namespace cmdline {
+
+
+/// Base exception for cmdline errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Generic error to describe problems caused by the user.
+class usage_error : public error {
+public:
+ explicit usage_error(const std::string&);
+ ~usage_error(void) throw();
+};
+
+
+/// Error denoting that no argument was provided to an option that required one.
+class missing_option_argument_error : public usage_error {
+ /// Name of the option for which no required argument was specified.
+ std::string _option;
+
+public:
+ explicit missing_option_argument_error(const std::string&);
+ ~missing_option_argument_error(void) throw();
+
+ const std::string& option(void) const;
+};
+
+
+/// Error denoting that the argument provided to an option is invalid.
+class option_argument_value_error : public usage_error {
+ /// Name of the option for which the argument was invalid.
+ std::string _option;
+
+ /// Raw value of the invalid user-provided argument.
+ std::string _argument;
+
+ /// Reason describing why the argument is invalid.
+ std::string _reason;
+
+public:
+ explicit option_argument_value_error(const std::string&, const std::string&,
+ const std::string&);
+ ~option_argument_value_error(void) throw();
+
+ const std::string& option(void) const;
+ const std::string& argument(void) const;
+};
+
+
+/// Error denoting that the user specified an unknown option.
+class unknown_option_error : public usage_error {
+ /// Name of the option that was not known.
+ std::string _option;
+
+public:
+ explicit unknown_option_error(const std::string&);
+ ~unknown_option_error(void) throw();
+
+ const std::string& option(void) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_EXCEPTIONS_HPP)
diff --git a/utils/cmdline/exceptions_test.cpp b/utils/cmdline/exceptions_test.cpp
new file mode 100644
index 000000000000..b541e08f6995
--- /dev/null
+++ b/utils/cmdline/exceptions_test.cpp
@@ -0,0 +1,83 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+namespace cmdline = utils::cmdline;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const cmdline::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error);
+ATF_TEST_CASE_BODY(missing_option_argument_error)
+{
+ const cmdline::missing_option_argument_error e("-o");
+ ATF_REQUIRE(std::strcmp("Missing required argument for option -o",
+ e.what()) == 0);
+ ATF_REQUIRE_EQ("-o", e.option());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_argument_value_error);
+ATF_TEST_CASE_BODY(option_argument_value_error)
+{
+ const cmdline::option_argument_value_error e("--the_option", "the value",
+ "the reason");
+ ATF_REQUIRE(std::strcmp("Invalid argument 'the value' for option "
+ "--the_option: the reason", e.what()) == 0);
+ ATF_REQUIRE_EQ("--the_option", e.option());
+ ATF_REQUIRE_EQ("the value", e.argument());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error);
+ATF_TEST_CASE_BODY(unknown_option_error)
+{
+ const cmdline::unknown_option_error e("--foo");
+ ATF_REQUIRE(std::strcmp("Unknown option --foo", e.what()) == 0);
+ ATF_REQUIRE_EQ("--foo", e.option());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error);
+ ATF_ADD_TEST_CASE(tcs, option_argument_value_error);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error);
+}
diff --git a/utils/cmdline/globals.cpp b/utils/cmdline/globals.cpp
new file mode 100644
index 000000000000..76e0231fa36b
--- /dev/null
+++ b/utils/cmdline/globals.cpp
@@ -0,0 +1,78 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/globals.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// The name of the binary used to execute the program.
+static std::string Progname;
+
+
+} // anonymous namespace
+
+
+/// Initializes the global state of the CLI.
+///
+/// This function can only be called once during the execution of a program,
+/// unless override_for_testing is set to true.
+///
+/// \param argv0 The value of argv[0]; i.e. the program name.
+/// \param override_for_testing Should always be set to false unless for tests
+/// of this functionality, which may set this to true to redefine internal
+/// state.
+void
+cmdline::init(const char* argv0, const bool override_for_testing)
+{
+ if (!override_for_testing)
+ PRE_MSG(Progname.empty(), "cmdline::init called more than once");
+ Progname = utils::fs::path(argv0).leaf_name();
+ LD(F("Program name: %s") % Progname);
+ POST(!Progname.empty());
+}
+
+
+/// Gets the program name.
+///
+/// \pre init() must have been called in advance.
+///
+/// \return The program name.
+const std::string&
+cmdline::progname(void)
+{
+ PRE_MSG(!Progname.empty(), "cmdline::init not called yet");
+ return Progname;
+}
diff --git a/utils/cmdline/globals.hpp b/utils/cmdline/globals.hpp
new file mode 100644
index 000000000000..ab7904d69520
--- /dev/null
+++ b/utils/cmdline/globals.hpp
@@ -0,0 +1,48 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/globals.hpp
+/// Representation of global, immutable state for a CLI.
+
+#if !defined(UTILS_CMDLINE_GLOBALS_HPP)
+#define UTILS_CMDLINE_GLOBALS_HPP
+
+#include <string>
+
+namespace utils {
+namespace cmdline {
+
+
+void init(const char*, const bool = false);
+const std::string& progname(void);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_GLOBALS_HPP)
diff --git a/utils/cmdline/globals_test.cpp b/utils/cmdline/globals_test.cpp
new file mode 100644
index 000000000000..5c2ac7cc2d6c
--- /dev/null
+++ b/utils/cmdline/globals_test.cpp
@@ -0,0 +1,77 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/globals.hpp"
+
+#include <atf-c++.hpp>
+
+namespace cmdline = utils::cmdline;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__absolute);
+ATF_TEST_CASE_BODY(progname__absolute)
+{
+ cmdline::init("/path/to/foobar");
+ ATF_REQUIRE_EQ("foobar", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__relative);
+ATF_TEST_CASE_BODY(progname__relative)
+{
+ cmdline::init("to/barbaz");
+ ATF_REQUIRE_EQ("barbaz", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__plain);
+ATF_TEST_CASE_BODY(progname__plain)
+{
+ cmdline::init("program");
+ ATF_REQUIRE_EQ("program", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__override_for_testing);
+ATF_TEST_CASE_BODY(progname__override_for_testing)
+{
+ cmdline::init("program");
+ ATF_REQUIRE_EQ("program", cmdline::progname());
+
+ cmdline::init("foo", true);
+ ATF_REQUIRE_EQ("foo", cmdline::progname());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, progname__absolute);
+ ATF_ADD_TEST_CASE(tcs, progname__relative);
+ ATF_ADD_TEST_CASE(tcs, progname__plain);
+ ATF_ADD_TEST_CASE(tcs, progname__override_for_testing);
+}
diff --git a/utils/cmdline/options.cpp b/utils/cmdline/options.cpp
new file mode 100644
index 000000000000..61736e31c11e
--- /dev/null
+++ b/utils/cmdline/options.cpp
@@ -0,0 +1,605 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/options.hpp"
+
+#include <stdexcept>
+#include <vector>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/operations.ipp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+
+/// Constructs a generic option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ If not NULL, specifies that the option must receive an
+/// argument and specifies the name of such argument for documentation
+/// purposes.
+/// \param default_value_ If not NULL, specifies that the option has a default
+/// value for the mandatory argument.
+cmdline::base_option::base_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ _short_name(short_name_),
+ _long_name(long_name_),
+ _description(description_),
+ _arg_name(arg_name_ == NULL ? "" : arg_name_),
+ _has_default_value(default_value_ != NULL),
+ _default_value(default_value_ == NULL ? "" : default_value_)
+{
+ INV(short_name_ != '\0');
+}
+
+
+/// Constructs a generic option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ If not NULL, specifies that the option must receive an
+/// argument and specifies the name of such argument for documentation
+/// purposes.
+/// \param default_value_ If not NULL, specifies that the option has a default
+/// value for the mandatory argument.
+cmdline::base_option::base_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ _short_name('\0'),
+ _long_name(long_name_),
+ _description(description_),
+ _arg_name(arg_name_ == NULL ? "" : arg_name_),
+ _has_default_value(default_value_ != NULL),
+ _default_value(default_value_ == NULL ? "" : default_value_)
+{
+}
+
+
+/// Destructor for the option.
+cmdline::base_option::~base_option(void)
+{
+}
+
+
+/// Checks whether the option has a short name or not.
+///
+/// \return True if the option has a short name, false otherwise.
+bool
+cmdline::base_option::has_short_name(void) const
+{
+ return _short_name != '\0';
+}
+
+
+/// Returns the short name of the option.
+///
+/// \pre has_short_name() must be true.
+///
+/// \return The short name.
+char
+cmdline::base_option::short_name(void) const
+{
+ PRE(has_short_name());
+ return _short_name;
+}
+
+
+/// Returns the long name of the option.
+///
+/// \return The long name.
+const std::string&
+cmdline::base_option::long_name(void) const
+{
+ return _long_name;
+}
+
+
+/// Returns the description of the option.
+///
+/// \return The description.
+const std::string&
+cmdline::base_option::description(void) const
+{
+ return _description;
+}
+
+
+/// Checks whether the option needs an argument or not.
+///
+/// \return True if the option needs an argument, false otherwise.
+bool
+cmdline::base_option::needs_arg(void) const
+{
+ return !_arg_name.empty();
+}
+
+
+/// Returns the argument name of the option for documentation purposes.
+///
+/// \pre needs_arg() must be true.
+///
+/// \return The argument name.
+const std::string&
+cmdline::base_option::arg_name(void) const
+{
+ INV(needs_arg());
+ return _arg_name;
+}
+
+
+/// Checks whether the option has a default value for its argument.
+///
+/// \pre needs_arg() must be true.
+///
+/// \return True if the option has a default value, false otherwise.
+bool
+cmdline::base_option::has_default_value(void) const
+{
+ PRE(needs_arg());
+ return _has_default_value;
+}
+
+
+/// Returns the default value for the argument to the option.
+///
+/// \pre has_default_value() must be true.
+///
+/// \return The default value.
+const std::string&
+cmdline::base_option::default_value(void) const
+{
+ INV(has_default_value());
+ return _default_value;;
+}
+
+
+/// Formats the short name of the option for documentation purposes.
+///
+/// \return A string describing the option's short name.
+std::string
+cmdline::base_option::format_short_name(void) const
+{
+ PRE(has_short_name());
+
+ if (needs_arg()) {
+ return F("-%s %s") % short_name() % arg_name();
+ } else {
+ return F("-%s") % short_name();
+ }
+}
+
+
+/// Formats the long name of the option for documentation purposes.
+///
+/// \return A string describing the option's long name.
+std::string
+cmdline::base_option::format_long_name(void) const
+{
+ if (needs_arg()) {
+ return F("--%s=%s") % long_name() % arg_name();
+ } else {
+ return F("--%s") % long_name();
+ }
+}
+
+
+
+/// Ensures that an argument passed to the option is valid.
+///
+/// This must be reimplemented by subclasses that describe options with
+/// arguments.
+///
+/// \throw cmdline::option_argument_value_error Subclasses must raise this
+/// exception to indicate the cases in which str is invalid.
+void
+cmdline::base_option::validate(const std::string& /* str */) const
+{
+ UNREACHABLE_MSG("Option does not support an argument");
+}
+
+
+/// Constructs a boolean option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+cmdline::bool_option::bool_option(const char short_name_,
+ const char* long_name_,
+ const char* description_) :
+ base_option(short_name_, long_name_, description_)
+{
+}
+
+
+/// Constructs a boolean option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+cmdline::bool_option::bool_option(const char* long_name_,
+ const char* description_) :
+ base_option(long_name_, description_)
+{
+}
+
+
+/// Constructs an integer option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::int_option::int_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs an integer option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::int_option::int_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that an integer argument passed to the int_option is valid.
+///
+/// \param raw_value The argument representing an integer as provided by the
+/// user.
+///
+/// \throw cmdline::option_argument_value_error If the integer provided in
+/// raw_value is invalid.
+void
+cmdline::int_option::validate(const std::string& raw_value) const
+{
+ try {
+ (void)text::to_type< int >(raw_value);
+ } catch (const std::runtime_error& e) {
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Not a valid integer");
+ }
+}
+
+
+/// Converts an integer argument to a native integer.
+///
+/// \param raw_value The argument representing an integer as provided by the
+/// user.
+///
+/// \return The integer.
+///
+/// \pre validate(raw_value) must be true.
+int
+cmdline::int_option::convert(const std::string& raw_value)
+{
+ try {
+ return text::to_type< int >(raw_value);
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for int option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a list option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::list_option::list_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a list option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::list_option::list_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that a lisstring argument passed to the list_option is valid.
+void
+cmdline::list_option::validate(
+ const std::string& /* raw_value */) const
+{
+ // Any list is potentially valid; the caller must check for semantics.
+}
+
+
+/// Converts a string argument to a vector.
+///
+/// \param raw_value The argument representing a list as provided by the user.
+///
+/// \return The list.
+///
+/// \pre validate(raw_value) must be true.
+cmdline::list_option::option_type
+cmdline::list_option::convert(const std::string& raw_value)
+{
+ try {
+ return text::split(raw_value, ',');
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for list option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a path option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::path_option::path_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a path option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::path_option::path_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that a path argument passed to the path_option is valid.
+///
+/// \param raw_value The argument representing a path as provided by the user.
+///
+/// \throw cmdline::option_argument_value_error If the path provided in
+/// raw_value is invalid.
+void
+cmdline::path_option::validate(const std::string& raw_value) const
+{
+ try {
+ (void)utils::fs::path(raw_value);
+ } catch (const utils::fs::error& e) {
+ throw cmdline::option_argument_value_error(F("--%s") % long_name(),
+ raw_value, e.what());
+ }
+}
+
+
+/// Converts a path argument to a utils::fs::path.
+///
+/// \param raw_value The argument representing a path as provided by the user.
+///
+/// \return The path.
+///
+/// \pre validate(raw_value) must be true.
+utils::fs::path
+cmdline::path_option::convert(const std::string& raw_value)
+{
+ try {
+ return utils::fs::path(raw_value);
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for path option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a property option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes. Must include the '=' delimiter.
+cmdline::property_option::property_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_) :
+ base_option(short_name_, long_name_, description_, arg_name_)
+{
+ PRE(arg_name().find('=') != std::string::npos);
+}
+
+
+/// Constructs a property option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes. Must include the '=' delimiter.
+cmdline::property_option::property_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_) :
+ base_option(long_name_, description_, arg_name_)
+{
+ PRE(arg_name().find('=') != std::string::npos);
+}
+
+
+/// Validates the argument to a property option.
+///
+/// \param raw_value The argument provided by the user.
+void
+cmdline::property_option::validate(const std::string& raw_value) const
+{
+ const std::string::size_type pos = raw_value.find('=');
+ if (pos == std::string::npos)
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value,
+ F("Argument does not have the form '%s'") % arg_name());
+
+ const std::string key = raw_value.substr(0, pos);
+ if (key.empty())
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Empty property name");
+
+ const std::string value = raw_value.substr(pos + 1);
+ if (value.empty())
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Empty value");
+}
+
+
+/// Returns the property option in a key/value pair form.
+///
+/// \param raw_value The argument provided by the user.
+///
+/// \return raw_value The key/value pair representation of the property.
+///
+/// \pre validate(raw_value) must be true.
+cmdline::property_option::option_type
+cmdline::property_option::convert(const std::string& raw_value)
+{
+ const std::string::size_type pos = raw_value.find('=');
+ return std::make_pair(raw_value.substr(0, pos), raw_value.substr(pos + 1));
+}
+
+
+/// Constructs a string option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::string_option::string_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a string option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::string_option::string_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Does nothing; all string values are valid arguments to a string_option.
+void
+cmdline::string_option::validate(
+ const std::string& /* raw_value */) const
+{
+ // Nothing to do.
+}
+
+
+/// Returns the string unmodified.
+///
+/// \param raw_value The argument provided by the user.
+///
+/// \return raw_value
+///
+/// \pre validate(raw_value) must be true.
+std::string
+cmdline::string_option::convert(const std::string& raw_value)
+{
+ return raw_value;
+}
diff --git a/utils/cmdline/options.hpp b/utils/cmdline/options.hpp
new file mode 100644
index 000000000000..f3a83889e491
--- /dev/null
+++ b/utils/cmdline/options.hpp
@@ -0,0 +1,237 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/options.hpp
+/// Definitions of command-line options.
+
+#if !defined(UTILS_CMDLINE_OPTIONS_HPP)
+#define UTILS_CMDLINE_OPTIONS_HPP
+
+#include "utils/cmdline/options_fwd.hpp"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Type-less base option class.
+///
+/// This abstract class provides the most generic representation of options. It
+/// allows defining options with both short and long names, with and without
+/// arguments and with and without optional values. These are all the possible
+/// combinations supported by the getopt_long(3) function, on which this is
+/// built.
+///
+/// The internal values (e.g. the default value) of a generic option are all
+/// represented as strings. However, from the caller's perspective, this is
+/// suboptimal. Hence why this class must be specialized: the subclasses
+/// provide type-specific accessors and provide automatic validation of the
+/// types (e.g. a string '3foo' is not passed to an integer option).
+///
+/// Given that subclasses are used through templatized code, they must provide:
+///
+/// <ul>
+/// <li>A public option_type typedef that defines the type of the
+/// option.</li>
+///
+/// <li>A convert() method that takes a string and converts it to
+/// option_type. The string can be assumed to be convertible to the
+/// destination type. Should not raise exceptions.</li>
+///
+/// <li>A validate() method that matches the implementation of convert().
+/// This method can throw option_argument_value_error if the string cannot
+/// be converted appropriately. If validate() does not throw, then
+/// convert() must execute successfully.</li>
+/// </ul>
+///
+/// TODO(jmmv): Many methods in this class are split into two parts: has_foo()
+/// and foo(), the former to query if the foo is available and the latter to get
+/// the foo. It'd be very nice if we'd use something similar Boost.Optional to
+/// simplify this interface altogether.
+class base_option {
+ /// Short name of the option; 0 to indicate that none is available.
+ char _short_name;
+
+ /// Long name of the option.
+ std::string _long_name;
+
+ /// Textual description of the purpose of the option.
+ std::string _description;
+
+ /// Descriptive name of the required argument; empty if not allowed.
+ std::string _arg_name;
+
+ /// Whether the option has a default value or not.
+ ///
+ /// \todo We should probably be using the optional class here.
+ bool _has_default_value;
+
+ /// If _has_default_value is true, the default value.
+ std::string _default_value;
+
+public:
+ base_option(const char, const char*, const char*, const char* = NULL,
+ const char* = NULL);
+ base_option(const char*, const char*, const char* = NULL,
+ const char* = NULL);
+ virtual ~base_option(void);
+
+ bool has_short_name(void) const;
+ char short_name(void) const;
+ const std::string& long_name(void) const;
+ const std::string& description(void) const;
+
+ bool needs_arg(void) const;
+ const std::string& arg_name(void) const;
+
+ bool has_default_value(void) const;
+ const std::string& default_value(void) const;
+
+ std::string format_short_name(void) const;
+ std::string format_long_name(void) const;
+
+ virtual void validate(const std::string&) const;
+};
+
+
+/// Definition of a boolean option.
+///
+/// A boolean option can be specified once in the command line, at which point
+/// is set to true. Such an option cannot carry optional arguments.
+class bool_option : public base_option {
+public:
+ bool_option(const char, const char*, const char*);
+ bool_option(const char*, const char*);
+ virtual ~bool_option(void) {}
+
+ /// The data type of this option.
+ typedef bool option_type;
+};
+
+
+/// Definition of an integer option.
+class int_option : public base_option {
+public:
+ int_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ int_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~int_option(void) {}
+
+ /// The data type of this option.
+ typedef int option_type;
+
+ virtual void validate(const std::string& str) const;
+ static int convert(const std::string& str);
+};
+
+
+/// Definition of a comma-separated list of strings.
+class list_option : public base_option {
+public:
+ list_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ list_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~list_option(void) {}
+
+ /// The data type of this option.
+ typedef std::vector< std::string > option_type;
+
+ virtual void validate(const std::string&) const;
+ static option_type convert(const std::string&);
+};
+
+
+/// Definition of an option representing a path.
+///
+/// The path pointed to by the option may not exist, but it must be
+/// syntactically valid.
+class path_option : public base_option {
+public:
+ path_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ path_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~path_option(void) {}
+
+ /// The data type of this option.
+ typedef utils::fs::path option_type;
+
+ virtual void validate(const std::string&) const;
+ static utils::fs::path convert(const std::string&);
+};
+
+
+/// Definition of a property option.
+///
+/// A property option is an option whose required arguments are of the form
+/// 'name=value'. Both components of the property are treated as free-form
+/// non-empty strings; any other validation must happen on the caller side.
+///
+/// \todo Would be nice if the delimiter was parametrizable. With the current
+/// parser interface (convert() being a static method), the only way to do
+/// this would be to templatize this class.
+class property_option : public base_option {
+public:
+ property_option(const char, const char*, const char*, const char*);
+ property_option(const char*, const char*, const char*);
+ virtual ~property_option(void) {}
+
+ /// The data type of this option.
+ typedef std::pair< std::string, std::string > option_type;
+
+ virtual void validate(const std::string& str) const;
+ static option_type convert(const std::string& str);
+};
+
+
+/// Definition of a free-form string option.
+///
+/// This class provides no restrictions on the argument passed to the option.
+class string_option : public base_option {
+public:
+ string_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ string_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~string_option(void) {}
+
+ /// The data type of this option.
+ typedef std::string option_type;
+
+ virtual void validate(const std::string& str) const;
+ static std::string convert(const std::string& str);
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_OPTIONS_HPP)
diff --git a/utils/cmdline/options_fwd.hpp b/utils/cmdline/options_fwd.hpp
new file mode 100644
index 000000000000..8b45797e3920
--- /dev/null
+++ b/utils/cmdline/options_fwd.hpp
@@ -0,0 +1,51 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/options_fwd.hpp
+/// Forward declarations for utils/cmdline/options.hpp
+
+#if !defined(UTILS_CMDLINE_OPTIONS_FWD_HPP)
+#define UTILS_CMDLINE_OPTIONS_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class base_option;
+class bool_option;
+class int_option;
+class list_option;
+class path_option;
+class property_option;
+class string_option;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_OPTIONS_FWD_HPP)
diff --git a/utils/cmdline/options_test.cpp b/utils/cmdline/options_test.cpp
new file mode 100644
index 000000000000..82fd706a191a
--- /dev/null
+++ b/utils/cmdline/options_test.cpp
@@ -0,0 +1,526 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/options.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/fs/path.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// Simple string-based option type for testing purposes.
+class mock_option : public cmdline::base_option {
+public:
+ /// Constructs a mock option with a short name and a long name.
+ ///
+ ///
+ /// \param short_name_ The short name for the option.
+ /// \param long_name_ The long name for the option.
+ /// \param description_ A user-friendly description for the option.
+ /// \param arg_name_ If not NULL, specifies that the option must receive an
+ /// argument and specifies the name of such argument for documentation
+ /// purposes.
+ /// \param default_value_ If not NULL, specifies that the option has a
+ /// default value for the mandatory argument.
+ mock_option(const char short_name_, const char* long_name_,
+ const char* description_, const char* arg_name_ = NULL,
+ const char* default_value_ = NULL) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_) {}
+
+ /// Constructs a mock option with a long name only.
+ ///
+ /// \param long_name_ The long name for the option.
+ /// \param description_ A user-friendly description for the option.
+ /// \param arg_name_ If not NULL, specifies that the option must receive an
+ /// argument and specifies the name of such argument for documentation
+ /// purposes.
+ /// \param default_value_ If not NULL, specifies that the option has a
+ /// default value for the mandatory argument.
+ mock_option(const char* long_name_,
+ const char* description_, const char* arg_name_ = NULL,
+ const char* default_value_ = NULL) :
+ base_option(long_name_, description_, arg_name_, default_value_) {}
+
+ /// The data type of this option.
+ typedef std::string option_type;
+
+ /// Ensures that the argument passed to the option is valid.
+ ///
+ /// In this particular mock option, this does not perform any validation.
+ void
+ validate(const std::string& /* str */) const
+ {
+ // Do nothing.
+ }
+
+ /// Returns the input parameter without any conversion.
+ ///
+ /// \param str The user-provided argument to the option.
+ ///
+ /// \return The same value as provided by the user without conversion.
+ static std::string
+ convert(const std::string& str)
+ {
+ return str;
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__no_arg);
+ATF_TEST_CASE_BODY(base_option__short_name__no_arg)
+{
+ const mock_option o('f', "force", "Force execution");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('f', o.short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+ ATF_REQUIRE_EQ("-f", o.format_short_name());
+ ATF_REQUIRE_EQ("--force", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__with_arg__no_default);
+ATF_TEST_CASE_BODY(base_option__short_name__with_arg__no_default)
+{
+ const mock_option o('c', "conf_file", "Configuration file", "path");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('c', o.short_name());
+ ATF_REQUIRE_EQ("conf_file", o.long_name());
+ ATF_REQUIRE_EQ("Configuration file", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+ ATF_REQUIRE_EQ("-c path", o.format_short_name());
+ ATF_REQUIRE_EQ("--conf_file=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__with_arg__with_default);
+ATF_TEST_CASE_BODY(base_option__short_name__with_arg__with_default)
+{
+ const mock_option o('c', "conf_file", "Configuration file", "path",
+ "defpath");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('c', o.short_name());
+ ATF_REQUIRE_EQ("conf_file", o.long_name());
+ ATF_REQUIRE_EQ("Configuration file", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("defpath", o.default_value());
+ ATF_REQUIRE_EQ("-c path", o.format_short_name());
+ ATF_REQUIRE_EQ("--conf_file=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__no_arg);
+ATF_TEST_CASE_BODY(base_option__long_name__no_arg)
+{
+ const mock_option o("dryrun", "Dry run mode");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("dryrun", o.long_name());
+ ATF_REQUIRE_EQ("Dry run mode", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+ ATF_REQUIRE_EQ("--dryrun", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__with_arg__no_default);
+ATF_TEST_CASE_BODY(base_option__long_name__with_arg__no_default)
+{
+ const mock_option o("helper", "Path to helper", "path");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("helper", o.long_name());
+ ATF_REQUIRE_EQ("Path to helper", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+ ATF_REQUIRE_EQ("--helper=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__with_arg__with_default);
+ATF_TEST_CASE_BODY(base_option__long_name__with_arg__with_default)
+{
+ const mock_option o("executable", "Executable name", "file", "foo");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("executable", o.long_name());
+ ATF_REQUIRE_EQ("Executable name", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("file", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("foo", o.default_value());
+ ATF_REQUIRE_EQ("--executable=file", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_option__short_name);
+ATF_TEST_CASE_BODY(bool_option__short_name)
+{
+ const cmdline::bool_option o('f', "force", "Force execution");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('f', o.short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_option__long_name);
+ATF_TEST_CASE_BODY(bool_option__long_name)
+{
+ const cmdline::bool_option o("force", "Force execution");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__short_name);
+ATF_TEST_CASE_BODY(int_option__short_name)
+{
+ const cmdline::int_option o('p', "int", "The int", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("int", o.long_name());
+ ATF_REQUIRE_EQ("The int", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__long_name);
+ATF_TEST_CASE_BODY(int_option__long_name)
+{
+ const cmdline::int_option o("int", "The int", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("int", o.long_name());
+ ATF_REQUIRE_EQ("The int", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__type);
+ATF_TEST_CASE_BODY(int_option__type)
+{
+ const cmdline::int_option o("int", "The int", "arg");
+
+ o.validate("123");
+ ATF_REQUIRE_EQ(123, cmdline::int_option::convert("123"));
+
+ o.validate("-567");
+ ATF_REQUIRE_EQ(-567, cmdline::int_option::convert("-567"));
+
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate(""));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5a"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("a5"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5 a"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5.0"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__short_name);
+ATF_TEST_CASE_BODY(list_option__short_name)
+{
+ const cmdline::list_option o('p', "list", "The list", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("list", o.long_name());
+ ATF_REQUIRE_EQ("The list", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__long_name);
+ATF_TEST_CASE_BODY(list_option__long_name)
+{
+ const cmdline::list_option o("list", "The list", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("list", o.long_name());
+ ATF_REQUIRE_EQ("The list", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__type);
+ATF_TEST_CASE_BODY(list_option__type)
+{
+ const cmdline::list_option o("list", "The list", "arg");
+
+ o.validate("");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("");
+ ATF_REQUIRE(words.empty());
+ }
+
+ o.validate("foo");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo");
+ ATF_REQUIRE_EQ(1, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ }
+
+ o.validate("foo,bar,baz");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,bar,baz");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("bar", words[1]);
+ ATF_REQUIRE_EQ("baz", words[2]);
+ }
+
+ o.validate("foo,bar,");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,bar,");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("bar", words[1]);
+ ATF_REQUIRE_EQ("", words[2]);
+ }
+
+ o.validate(",foo,bar");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert(",foo,bar");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("", words[0]);
+ ATF_REQUIRE_EQ("foo", words[1]);
+ ATF_REQUIRE_EQ("bar", words[2]);
+ }
+
+ o.validate("foo,,bar");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,,bar");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("", words[1]);
+ ATF_REQUIRE_EQ("bar", words[2]);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__short_name);
+ATF_TEST_CASE_BODY(path_option__short_name)
+{
+ const cmdline::path_option o('p', "path", "The path", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("path", o.long_name());
+ ATF_REQUIRE_EQ("The path", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__long_name);
+ATF_TEST_CASE_BODY(path_option__long_name)
+{
+ const cmdline::path_option o("path", "The path", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("path", o.long_name());
+ ATF_REQUIRE_EQ("The path", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__type);
+ATF_TEST_CASE_BODY(path_option__type)
+{
+ const cmdline::path_option o("path", "The path", "arg");
+
+ o.validate("/some/path");
+
+ try {
+ o.validate("");
+ fail("option_argument_value_error not raised");
+ } catch (const cmdline::option_argument_value_error& e) {
+ // Expected; ignore.
+ }
+
+ const cmdline::path_option::option_type path =
+ cmdline::path_option::convert("/foo/bar");
+ ATF_REQUIRE_EQ("bar", path.leaf_name()); // Ensure valid type.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__short_name);
+ATF_TEST_CASE_BODY(property_option__short_name)
+{
+ const cmdline::property_option o('p', "property", "The property", "a=b");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("property", o.long_name());
+ ATF_REQUIRE_EQ("The property", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("a=b", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__long_name);
+ATF_TEST_CASE_BODY(property_option__long_name)
+{
+ const cmdline::property_option o("property", "The property", "a=b");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("property", o.long_name());
+ ATF_REQUIRE_EQ("The property", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("a=b", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__type);
+ATF_TEST_CASE_BODY(property_option__type)
+{
+ typedef std::pair< std::string, std::string > string_pair;
+ const cmdline::property_option o("property", "The property", "a=b");
+
+ o.validate("foo=bar");
+ ATF_REQUIRE(string_pair("foo", "bar") ==
+ cmdline::property_option::convert("foo=bar"));
+
+ o.validate(" foo = bar baz");
+ ATF_REQUIRE(string_pair(" foo ", " bar baz") ==
+ cmdline::property_option::convert(" foo = bar baz"));
+
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate(""));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("="));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("a="));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("=b"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__short_name);
+ATF_TEST_CASE_BODY(string_option__short_name)
+{
+ const cmdline::string_option o('p', "string", "The string", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("string", o.long_name());
+ ATF_REQUIRE_EQ("The string", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__long_name);
+ATF_TEST_CASE_BODY(string_option__long_name)
+{
+ const cmdline::string_option o("string", "The string", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("string", o.long_name());
+ ATF_REQUIRE_EQ("The string", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__type);
+ATF_TEST_CASE_BODY(string_option__type)
+{
+ const cmdline::string_option o("string", "The string", "foo");
+
+ o.validate("");
+ o.validate("some string");
+
+ const cmdline::string_option::option_type string =
+ cmdline::string_option::convert("foo");
+ ATF_REQUIRE_EQ(3, string.length()); // Ensure valid type.
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__no_arg);
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__with_arg__no_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__with_arg__with_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__no_arg);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__with_arg__no_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__with_arg__with_default);
+
+ ATF_ADD_TEST_CASE(tcs, bool_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, bool_option__long_name);
+
+ ATF_ADD_TEST_CASE(tcs, int_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, int_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, int_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, list_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, list_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, list_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, path_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, path_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, path_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, property_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, property_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, property_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, string_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, string_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, string_option__type);
+}
diff --git a/utils/cmdline/parser.cpp b/utils/cmdline/parser.cpp
new file mode 100644
index 000000000000..5c83f6d69cc4
--- /dev/null
+++ b/utils/cmdline/parser.cpp
@@ -0,0 +1,385 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/parser.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <getopt.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+
+#include "utils/auto_array.ipp"
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// Auxiliary data to call getopt_long(3).
+struct getopt_data : utils::noncopyable {
+ /// Plain-text representation of the short options.
+ ///
+ /// This string follows the syntax expected by getopt_long(3) in the
+ /// argument to describe the short options.
+ std::string short_options;
+
+ /// Representation of the long options as expected by getopt_long(3).
+ utils::auto_array< ::option > long_options;
+
+ /// Auto-generated identifiers to be able to parse long options.
+ std::map< int, const cmdline::base_option* > ids;
+};
+
+
+/// Converts a cmdline::options_vector to a getopt_data.
+///
+/// \param options The high-level definition of the options.
+/// \param [out] data An object containing the necessary data to call
+/// getopt_long(3) and interpret its results.
+static void
+options_to_getopt_data(const cmdline::options_vector& options,
+ getopt_data& data)
+{
+ data.short_options.clear();
+ data.long_options.reset(new ::option[options.size() + 1]);
+
+ int cur_id = 512;
+
+ for (cmdline::options_vector::size_type i = 0; i < options.size(); i++) {
+ const cmdline::base_option* option = options[i];
+ ::option& long_option = data.long_options[i];
+
+ long_option.name = option->long_name().c_str();
+ if (option->needs_arg())
+ long_option.has_arg = required_argument;
+ else
+ long_option.has_arg = no_argument;
+
+ int id = -1;
+ if (option->has_short_name()) {
+ data.short_options += option->short_name();
+ if (option->needs_arg())
+ data.short_options += ':';
+ id = option->short_name();
+ } else {
+ id = cur_id++;
+ }
+ long_option.flag = NULL;
+ long_option.val = id;
+ data.ids[id] = option;
+ }
+
+ ::option& last_long_option = data.long_options[options.size()];
+ last_long_option.name = NULL;
+ last_long_option.has_arg = 0;
+ last_long_option.flag = NULL;
+ last_long_option.val = 0;
+}
+
+
+/// Converts an argc/argv pair to an args_vector.
+///
+/// \param argc The value of argc as passed to main().
+/// \param argv The value of argv as passed to main().
+///
+/// \return An args_vector with the same contents of argc/argv.
+static cmdline::args_vector
+argv_to_vector(int argc, const char* const argv[])
+{
+ PRE(argv[argc] == NULL);
+ cmdline::args_vector args;
+ for (int i = 0; i < argc; i++)
+ args.push_back(argv[i]);
+ return args;
+}
+
+
+/// Creates a mutable version of argv.
+///
+/// \param argc The value of argc as passed to main().
+/// \param argv The value of argv as passed to main().
+///
+/// \return A new argv, with mutable buffers. The returned array must be
+/// released using the free_mutable_argv() function.
+static char**
+make_mutable_argv(const int argc, const char* const* argv)
+{
+ char** mutable_argv = new char*[argc + 1];
+ for (int i = 0; i < argc; i++)
+ mutable_argv[i] = ::strdup(argv[i]);
+ mutable_argv[argc] = NULL;
+ return mutable_argv;
+}
+
+
+/// Releases the object returned by make_mutable_argv().
+///
+/// \param argv A dynamically-allocated argv as returned by make_mutable_argv().
+static void
+free_mutable_argv(char** argv)
+{
+ char** ptr = argv;
+ while (*ptr != NULL) {
+ ::free(*ptr);
+ ptr++;
+ }
+ delete [] argv;
+}
+
+
+/// Finds the name of the offending option after a getopt_long error.
+///
+/// \param data Our internal getopt data used for the call to getopt_long.
+/// \param getopt_optopt The value of getopt(3)'s optopt after the error.
+/// \param argv The argv passed to getopt_long.
+/// \param getopt_optind The value of getopt(3)'s optind after the error.
+///
+/// \return A fully-specified option name (i.e. an option name prefixed by
+/// either '-' or '--').
+static std::string
+find_option_name(const getopt_data& data, const int getopt_optopt,
+ char** argv, const int getopt_optind)
+{
+ PRE(getopt_optopt >= 0);
+
+ if (getopt_optopt == 0) {
+ return argv[getopt_optind - 1];
+ } else if (getopt_optopt < std::numeric_limits< char >::max()) {
+ INV(getopt_optopt > 0);
+ const char ch = static_cast< char >(getopt_optopt);
+ return F("-%s") % ch;
+ } else {
+ for (const ::option* opt = &data.long_options[0]; opt->name != NULL;
+ opt++) {
+ if (opt->val == getopt_optopt)
+ return F("--%s") % opt->name;
+ }
+ UNREACHABLE;
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new parsed_cmdline.
+///
+/// Use the cmdline::parse() free functions to construct.
+///
+/// \param option_values_ A mapping of long option names to values. This
+/// contains a representation of the options provided by the user. Note
+/// that each value is actually a collection values: a user may specify a
+/// flag multiple times, and depending on the case we want to honor one or
+/// the other. For those options that support no argument, the argument
+/// value is the empty string.
+/// \param arguments_ The list of non-option arguments in the command line.
+cmdline::parsed_cmdline::parsed_cmdline(
+ const std::map< std::string, std::vector< std::string > >& option_values_,
+ const cmdline::args_vector& arguments_) :
+ _option_values(option_values_),
+ _arguments(arguments_)
+{
+}
+
+
+/// Checks if the given option has been given in the command line.
+///
+/// \param name The long option name to check for presence.
+///
+/// \return True if the option has been given; false otherwise.
+bool
+cmdline::parsed_cmdline::has_option(const std::string& name) const
+{
+ return _option_values.find(name) != _option_values.end();
+}
+
+
+/// Gets the raw value of an option.
+///
+/// The raw value of an option is a collection of strings that represent all the
+/// values passed to the option on the command line. It is up to the consumer
+/// if he wants to honor only the last value or all of them.
+///
+/// The caller has to use get_option() instead; this function is internal.
+///
+/// \pre has_option(name) must be true.
+///
+/// \param name The option to query.
+///
+/// \return The value of the option as a plain string.
+const std::vector< std::string >&
+cmdline::parsed_cmdline::get_option_raw(const std::string& name) const
+{
+ std::map< std::string, std::vector< std::string > >::const_iterator iter =
+ _option_values.find(name);
+ INV_MSG(iter != _option_values.end(), F("Undefined option --%s") % name);
+ return (*iter).second;
+}
+
+
+/// Returns the non-option arguments found in the command line.
+///
+/// \return The arguments, if any.
+const cmdline::args_vector&
+cmdline::parsed_cmdline::arguments(void) const
+{
+ return _arguments;
+}
+
+
+/// Parses a command line.
+///
+/// \param args The command line to parse, broken down by words.
+/// \param options The description of the supported options.
+///
+/// \return The parsed command line.
+///
+/// \pre args[0] must be the program or command name.
+///
+/// \throw cmdline::error See the description of parse(argc, argv, options) for
+/// more details on the raised errors.
+cmdline::parsed_cmdline
+cmdline::parse(const cmdline::args_vector& args,
+ const cmdline::options_vector& options)
+{
+ PRE_MSG(args.size() >= 1, "No progname or command name found");
+
+ utils::auto_array< const char* > argv(new const char*[args.size() + 1]);
+ for (args_vector::size_type i = 0; i < args.size(); i++)
+ argv[i] = args[i].c_str();
+ argv[args.size()] = NULL;
+ return parse(static_cast< int >(args.size()), argv.get(), options);
+}
+
+
+/// Parses a command line.
+///
+/// \param argc The number of arguments in argv, without counting the
+/// terminating NULL.
+/// \param argv The arguments to parse. The array is NULL-terminated.
+/// \param options The description of the supported options.
+///
+/// \return The parsed command line.
+///
+/// \pre args[0] must be the program or command name.
+///
+/// \throw cmdline::missing_option_argument_error If the user specified an
+/// option that requires an argument, but no argument was provided.
+/// \throw cmdline::unknown_option_error If the user specified an unknown
+/// option (i.e. an option not defined in options).
+/// \throw cmdline::option_argument_value_error If the user passed an invalid
+/// argument to a supported option.
+cmdline::parsed_cmdline
+cmdline::parse(const int argc, const char* const* argv,
+ const cmdline::options_vector& options)
+{
+ PRE_MSG(argc >= 1, "No progname or command name found");
+
+ getopt_data data;
+ options_to_getopt_data(options, data);
+
+ std::map< std::string, std::vector< std::string > > option_values;
+
+ for (cmdline::options_vector::const_iterator iter = options.begin();
+ iter != options.end(); iter++) {
+ const cmdline::base_option* option = *iter;
+ if (option->needs_arg() && option->has_default_value())
+ option_values[option->long_name()].push_back(
+ option->default_value());
+ }
+
+ args_vector args;
+
+ int mutable_argc = argc;
+ char** mutable_argv = make_mutable_argv(argc, argv);
+ const int old_opterr = ::opterr;
+ try {
+ int ch;
+
+ ::opterr = 0;
+
+ while ((ch = ::getopt_long(mutable_argc, mutable_argv,
+ ("+:" + data.short_options).c_str(),
+ data.long_options.get(), NULL)) != -1) {
+ if (ch == ':' ) {
+ const std::string name = find_option_name(
+ data, ::optopt, mutable_argv, ::optind);
+ throw cmdline::missing_option_argument_error(name);
+ } else if (ch == '?') {
+ const std::string name = find_option_name(
+ data, ::optopt, mutable_argv, ::optind);
+ throw cmdline::unknown_option_error(name);
+ }
+
+ const std::map< int, const cmdline::base_option* >::const_iterator
+ id = data.ids.find(ch);
+ INV(id != data.ids.end());
+ const cmdline::base_option* option = (*id).second;
+
+ if (option->needs_arg()) {
+ if (::optarg != NULL) {
+ option->validate(::optarg);
+ option_values[option->long_name()].push_back(::optarg);
+ } else
+ INV(option->has_default_value());
+ } else {
+ option_values[option->long_name()].push_back("");
+ }
+ }
+ args = argv_to_vector(mutable_argc - optind, mutable_argv + optind);
+
+ ::opterr = old_opterr;
+ ::optind = GETOPT_OPTIND_RESET_VALUE;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+ } catch (...) {
+ free_mutable_argv(mutable_argv);
+ ::opterr = old_opterr;
+ ::optind = GETOPT_OPTIND_RESET_VALUE;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+ throw;
+ }
+ free_mutable_argv(mutable_argv);
+
+ return parsed_cmdline(option_values, args);
+}
diff --git a/utils/cmdline/parser.hpp b/utils/cmdline/parser.hpp
new file mode 100644
index 000000000000..657fd1f01dd3
--- /dev/null
+++ b/utils/cmdline/parser.hpp
@@ -0,0 +1,85 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/parser.hpp
+/// Routines and data types to parse command line options and arguments.
+
+#if !defined(UTILS_CMDLINE_PARSER_HPP)
+#define UTILS_CMDLINE_PARSER_HPP
+
+#include "utils/cmdline/parser_fwd.hpp"
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace cmdline {
+
+
+/// Representation of a parsed command line.
+///
+/// This class is returned by the command line parsing algorithm and provides
+/// methods to query the values of the options and the value of the arguments.
+/// All the values fed into this class can considered to be sane (i.e. the
+/// arguments to the options and the arguments to the command are valid), as all
+/// validation happens during parsing (before this class is instantiated).
+class parsed_cmdline {
+ /// Mapping of option names to all the values provided.
+ std::map< std::string, std::vector< std::string > > _option_values;
+
+ /// Collection of arguments with all options removed.
+ args_vector _arguments;
+
+ const std::vector< std::string >& get_option_raw(const std::string&) const;
+
+public:
+ parsed_cmdline(const std::map< std::string, std::vector< std::string > >&,
+ const args_vector&);
+
+ bool has_option(const std::string&) const;
+
+ template< typename Option >
+ typename Option::option_type get_option(const std::string&) const;
+
+ template< typename Option >
+ std::vector< typename Option::option_type > get_multi_option(
+ const std::string&) const;
+
+ const args_vector& arguments(void) const;
+};
+
+
+parsed_cmdline parse(const args_vector&, const options_vector&);
+parsed_cmdline parse(const int, const char* const*, const options_vector&);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_PARSER_HPP)
diff --git a/utils/cmdline/parser.ipp b/utils/cmdline/parser.ipp
new file mode 100644
index 000000000000..820826a15bfe
--- /dev/null
+++ b/utils/cmdline/parser.ipp
@@ -0,0 +1,83 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_CMDLINE_PARSER_IPP)
+#define UTILS_CMDLINE_PARSER_IPP
+
+#include "utils/cmdline/parser.hpp"
+
+
+/// Gets the value of an option.
+///
+/// If the option has been specified multiple times on the command line, this
+/// only returns the last value. This is the traditional behavior.
+///
+/// The option must support arguments. Otherwise, a call to this function will
+/// not compile because the option type will lack the definition of some fields
+/// and/or methods.
+///
+/// \param name The option to query.
+///
+/// \return The value of the option converted to the appropriate type.
+///
+/// \pre has_option(name) must be true.
+template< typename Option > typename Option::option_type
+utils::cmdline::parsed_cmdline::get_option(const std::string& name) const
+{
+ const std::vector< std::string >& raw_values = get_option_raw(name);
+ return Option::convert(raw_values[raw_values.size() - 1]);
+}
+
+
+/// Gets the values of an option that supports repetition.
+///
+/// The option must support arguments. Otherwise, a call to this function will
+/// not compile because the option type will lack the definition of some fields
+/// and/or methods.
+///
+/// \param name The option to query.
+///
+/// \return The values of the option converted to the appropriate type.
+///
+/// \pre has_option(name) must be true.
+template< typename Option > std::vector< typename Option::option_type >
+utils::cmdline::parsed_cmdline::get_multi_option(const std::string& name) const
+{
+ std::vector< typename Option::option_type > values;
+
+ const std::vector< std::string >& raw_values = get_option_raw(name);
+ for (std::vector< std::string >::const_iterator iter = raw_values.begin();
+ iter != raw_values.end(); iter++) {
+ values.push_back(Option::convert(*iter));
+ }
+
+ return values;
+}
+
+
+#endif // !defined(UTILS_CMDLINE_PARSER_IPP)
diff --git a/utils/cmdline/parser_fwd.hpp b/utils/cmdline/parser_fwd.hpp
new file mode 100644
index 000000000000..a136e99a47ac
--- /dev/null
+++ b/utils/cmdline/parser_fwd.hpp
@@ -0,0 +1,58 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/parser_fwd.hpp
+/// Forward declarations for utils/cmdline/parser.hpp
+
+#if !defined(UTILS_CMDLINE_PARSER_FWD_HPP)
+#define UTILS_CMDLINE_PARSER_FWD_HPP
+
+#include <string>
+#include <vector>
+
+#include "utils/cmdline/options_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Replacement for argc and argv to represent a command line.
+typedef std::vector< std::string > args_vector;
+
+
+/// Collection of options to be used during parsing.
+typedef std::vector< const base_option* > options_vector;
+
+
+class parsed_cmdline;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_PARSER_FWD_HPP)
diff --git a/utils/cmdline/parser_test.cpp b/utils/cmdline/parser_test.cpp
new file mode 100644
index 000000000000..96370d279d2e
--- /dev/null
+++ b/utils/cmdline/parser_test.cpp
@@ -0,0 +1,688 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/parser.ipp"
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+using cmdline::base_option;
+using cmdline::bool_option;
+using cmdline::int_option;
+using cmdline::parse;
+using cmdline::parsed_cmdline;
+using cmdline::string_option;
+
+
+namespace {
+
+
+/// Mock option type to check the validate and convert methods sequence.
+///
+/// Instances of this option accept a string argument that must be either "zero"
+/// or "one". These are validated and converted to integers.
+class mock_option : public base_option {
+public:
+ /// Constructs the new option.
+ ///
+ /// \param long_name_ The long name for the option. All other option
+ /// properties are irrelevant for the tests using this, so they are set
+ /// to arbitrary values.
+ mock_option(const char* long_name_) :
+ base_option(long_name_, "Irrelevant description", "arg")
+ {
+ }
+
+ /// The type of the argument of this option.
+ typedef int option_type;
+
+ /// Checks that the user-provided option is valid.
+ ///
+ /// \param str The user argument; must be "zero" or "one".
+ ///
+ /// \throw cmdline::option_argument_value_error If str is not valid.
+ void
+ validate(const std::string& str) const
+ {
+ if (str != "zero" && str != "one")
+ throw cmdline::option_argument_value_error(F("--%s") % long_name(),
+ str, "Unknown value");
+ }
+
+ /// Converts the user-provided argument to our native integer type.
+ ///
+ /// \param str The user argument; must be "zero" or "one".
+ ///
+ /// \return 0 if the input is "zero", or 1 if the input is "one".
+ ///
+ /// \throw std::runtime_error If str is not valid. In real life, this
+ /// should be a precondition because validate() has already ensured that
+ /// the values passed to convert() are correct. However, we raise an
+ /// exception here because we are actually validating that this code
+ /// sequence holds true.
+ static int
+ convert(const std::string& str)
+ {
+ if (str == "zero")
+ return 0;
+ else if (str == "one")
+ return 1;
+ else {
+ // This would generally be an assertion but, given that this is
+ // test code, we want to catch any errors regardless of how the
+ // binary is built.
+ throw std::runtime_error("Value not validated properly.");
+ }
+ }
+};
+
+
+/// Redirects stdout and stderr to a file.
+///
+/// This fails the test case in case of any error.
+///
+/// \param file The name of the file to redirect stdout and stderr to.
+///
+/// \return A copy of the old stdout and stderr file descriptors.
+static std::pair< int, int >
+mock_stdfds(const char* file)
+{
+ std::cout.flush();
+ std::cerr.flush();
+
+ const int oldout = ::dup(STDOUT_FILENO);
+ ATF_REQUIRE(oldout != -1);
+ const int olderr = ::dup(STDERR_FILENO);
+ ATF_REQUIRE(olderr != -1);
+
+ const int fd = ::open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ATF_REQUIRE(fd != -1);
+ ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
+ ATF_REQUIRE(::dup2(fd, STDERR_FILENO) != -1);
+ ::close(fd);
+
+ return std::make_pair(oldout, olderr);
+}
+
+
+/// Restores stdout and stderr after a call to mock_stdfds.
+///
+/// \param oldfds The copy of the previous stdout and stderr as returned by the
+/// call to mock_fds().
+static void
+restore_stdfds(const std::pair< int, int >& oldfds)
+{
+ ATF_REQUIRE(::dup2(oldfds.first, STDOUT_FILENO) != -1);
+ ::close(oldfds.first);
+ ATF_REQUIRE(::dup2(oldfds.second, STDERR_FILENO) != -1);
+ ::close(oldfds.second);
+}
+
+
+/// Checks whether a '+:' prefix to the short options of getopt_long works.
+///
+/// It turns out that the getopt_long(3) implementation of Ubuntu 10.04.1 (and
+/// very likely other distributions) does not properly report a missing argument
+/// to a second long option as such. Instead of returning ':' when the second
+/// long option provided on the command line does not carry a required argument,
+/// it will mistakenly return '?' which translates to "unknown option".
+///
+/// As a result of this bug, we cannot properly detect that 'flag2' requires an
+/// argument in a command line like: 'progname --flag1=foo --flag2'.
+///
+/// I am not sure if we could fully workaround the issue in the implementation
+/// of our library. For the time being I am just using this bug detection in
+/// the test cases to prevent failures that are not really our fault.
+///
+/// \return bool True if getopt_long is broken and does not interpret '+:'
+/// correctly; False otherwise.
+static bool
+is_getopt_long_pluscolon_broken(void)
+{
+ struct ::option long_options[] = {
+ { "flag1", 1, NULL, '1' },
+ { "flag2", 1, NULL, '2' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ const int argc = 3;
+ char* argv[4];
+ argv[0] = ::strdup("progname");
+ argv[1] = ::strdup("--flag1=a");
+ argv[2] = ::strdup("--flag2");
+ argv[3] = NULL;
+
+ const int old_opterr = ::opterr;
+ ::opterr = 0;
+
+ bool got_colon = false;
+
+ int opt;
+ while ((opt = ::getopt_long(argc, argv, "+:", long_options, NULL)) != -1) {
+ switch (opt) {
+ case '1': break;
+ case '2': break;
+ case ':': got_colon = true; break;
+ case '?': break;
+ default: UNREACHABLE; break;
+ }
+ }
+
+ ::opterr = old_opterr;
+ ::optind = 1;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+
+ for (char** arg = &argv[0]; *arg != NULL; arg++)
+ std::free(*arg);
+
+ return !got_colon;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__no_options);
+ATF_TEST_CASE_BODY(progname__no_options)
+{
+ const int argc = 1;
+ const char* const argv[] = {"progname", NULL};
+ std::vector< const base_option* > options;
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.arguments().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__some_options);
+ATF_TEST_CASE_BODY(progname__some_options)
+{
+ const int argc = 1;
+ const char* const argv[] = {"progname", NULL};
+ const string_option a('a', "a_option", "Foo", NULL);
+ const string_option b('b', "b_option", "Bar", "arg", "foo");
+ const string_option c("c_option", "Baz", NULL);
+ const string_option d("d_option", "Wohoo", "arg", "bar");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ options.push_back(&c);
+ options.push_back(&d);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE_EQ("foo", cmdline.get_option< string_option >("b_option"));
+ ATF_REQUIRE_EQ("bar", cmdline.get_option< string_option >("d_option"));
+ ATF_REQUIRE(cmdline.arguments().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_args__no_options);
+ATF_TEST_CASE_BODY(some_args__no_options)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "foo", "-c", "--opt", "bar", NULL};
+ std::vector< const base_option* > options;
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(!cmdline.has_option("c"));
+ ATF_REQUIRE(!cmdline.has_option("opt"));
+ ATF_REQUIRE_EQ(4, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("foo", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("-c", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("--opt", cmdline.arguments()[2]);
+ ATF_REQUIRE_EQ("bar", cmdline.arguments()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_args__some_options);
+ATF_TEST_CASE_BODY(some_args__some_options)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "foo", "-c", "--opt", "bar", NULL};
+ const string_option c('c', "opt", "Description", NULL);
+ std::vector< const base_option* > options;
+ options.push_back(&c);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(!cmdline.has_option("c"));
+ ATF_REQUIRE(!cmdline.has_option("opt"));
+ ATF_REQUIRE_EQ(4, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("foo", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("-c", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("--opt", cmdline.arguments()[2]);
+ ATF_REQUIRE_EQ("bar", cmdline.arguments()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_options__all_known);
+ATF_TEST_CASE_BODY(some_options__all_known)
+{
+ const int argc = 14;
+ const char* const argv[] = {
+ "progname",
+ "-a",
+ "-bvalue_b",
+ "-c", "value_c",
+ //"-d", // Options with default optional values are unsupported.
+ "-evalue_e", // Has default; overriden.
+ "--f_long",
+ "--g_long=value_g",
+ "--h_long", "value_h",
+ //"--i_long", // Options with default optional values are unsupported.
+ "--j_long", "value_j", // Has default; overriden as separate argument.
+ "arg1", "arg2", NULL,
+ };
+ const bool_option a('a', "a_long", "");
+ const string_option b('b', "b_long", "Description", "arg");
+ const string_option c('c', "c_long", "ABCD", "foo");
+ const string_option d('d', "d_long", "Description", "bar", "default_d");
+ const string_option e('e', "e_long", "Description", "baz", "default_e");
+ const bool_option f("f_long", "Description");
+ const string_option g("g_long", "Description", "arg");
+ const string_option h("h_long", "Description", "foo");
+ const string_option i("i_long", "EFGH", "bar", "default_i");
+ const string_option j("j_long", "Description", "baz", "default_j");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ options.push_back(&c);
+ options.push_back(&d);
+ options.push_back(&e);
+ options.push_back(&f);
+ options.push_back(&g);
+ options.push_back(&h);
+ options.push_back(&i);
+ options.push_back(&j);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.has_option("a_long"));
+ ATF_REQUIRE_EQ("value_b", cmdline.get_option< string_option >("b_long"));
+ ATF_REQUIRE_EQ("value_c", cmdline.get_option< string_option >("c_long"));
+ ATF_REQUIRE_EQ("default_d", cmdline.get_option< string_option >("d_long"));
+ ATF_REQUIRE_EQ("value_e", cmdline.get_option< string_option >("e_long"));
+ ATF_REQUIRE(cmdline.has_option("f_long"));
+ ATF_REQUIRE_EQ("value_g", cmdline.get_option< string_option >("g_long"));
+ ATF_REQUIRE_EQ("value_h", cmdline.get_option< string_option >("h_long"));
+ ATF_REQUIRE_EQ("default_i", cmdline.get_option< string_option >("i_long"));
+ ATF_REQUIRE_EQ("value_j", cmdline.get_option< string_option >("j_long"));
+ ATF_REQUIRE_EQ(2, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("arg1", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("arg2", cmdline.arguments()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_options__multi);
+ATF_TEST_CASE_BODY(some_options__multi)
+{
+ const int argc = 9;
+ const char* const argv[] = {
+ "progname",
+ "-a1",
+ "-bvalue1",
+ "-a2",
+ "--a_long=3",
+ "-bvalue2",
+ "--b_long=value3",
+ "arg1", "arg2", NULL,
+ };
+ const int_option a('a', "a_long", "Description", "arg");
+ const string_option b('b', "b_long", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ {
+ ATF_REQUIRE_EQ(3, cmdline.get_option< int_option >("a_long"));
+ const std::vector< int > multi =
+ cmdline.get_multi_option< int_option >("a_long");
+ ATF_REQUIRE_EQ(3, multi.size());
+ ATF_REQUIRE_EQ(1, multi[0]);
+ ATF_REQUIRE_EQ(2, multi[1]);
+ ATF_REQUIRE_EQ(3, multi[2]);
+ }
+
+ {
+ ATF_REQUIRE_EQ("value3", cmdline.get_option< string_option >("b_long"));
+ const std::vector< std::string > multi =
+ cmdline.get_multi_option< string_option >("b_long");
+ ATF_REQUIRE_EQ(3, multi.size());
+ ATF_REQUIRE_EQ("value1", multi[0]);
+ ATF_REQUIRE_EQ("value2", multi[1]);
+ ATF_REQUIRE_EQ("value3", multi[2]);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subcommands);
+ATF_TEST_CASE_BODY(subcommands)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "--flag1", "subcommand",
+ "--flag2", "arg", NULL};
+ const bool_option flag1("flag1", "");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE( cmdline.has_option("flag1"));
+ ATF_REQUIRE(!cmdline.has_option("flag2"));
+ ATF_REQUIRE_EQ(3, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("subcommand", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("--flag2", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("arg", cmdline.arguments()[2]);
+
+ const bool_option flag2("flag2", "");
+ std::vector< const base_option* > options2;
+ options2.push_back(&flag2);
+ const parsed_cmdline cmdline2 = parse(cmdline.arguments(), options2);
+
+ ATF_REQUIRE(!cmdline2.has_option("flag1"));
+ ATF_REQUIRE( cmdline2.has_option("flag2"));
+ ATF_REQUIRE_EQ(1, cmdline2.arguments().size());
+ ATF_REQUIRE_EQ("arg", cmdline2.arguments()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__short);
+ATF_TEST_CASE_BODY(missing_option_argument_error__short)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a3", "-b", NULL};
+ const string_option flag1('a', "flag1", "Description", "arg");
+ const string_option flag2('b', "flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("-b", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__shortblock);
+ATF_TEST_CASE_BODY(missing_option_argument_error__shortblock)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-ab3", "-ac", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ const string_option flag2('b', "flag2", "Description", "arg");
+ const string_option flag3('c', "flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+ options.push_back(&flag3);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("-c", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__long);
+ATF_TEST_CASE_BODY(missing_option_argument_error__long)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2", NULL};
+ const string_option flag1("flag1", "Description", "arg");
+ const string_option flag2("flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__short);
+ATF_TEST_CASE_BODY(unknown_option_error__short)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a", "-b", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-b", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__shortblock);
+ATF_TEST_CASE_BODY(unknown_option_error__shortblock)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a", "-bdc", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ const bool_option flag2('b', "flag2", "Description");
+ const bool_option flag3('c', "flag3", "Description");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+ options.push_back(&flag3);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-d", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__long);
+ATF_TEST_CASE_BODY(unknown_option_error__long)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2", NULL};
+ const string_option flag1("flag1", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_plus_option_error);
+ATF_TEST_CASE_BODY(unknown_plus_option_error)
+{
+ const int argc = 2;
+ const char* const argv[] = {"progname", "-+", NULL};
+ const cmdline::options_vector options;
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-+", e.option());
+ } catch (const cmdline::missing_option_argument_error& e) {
+ fail("Looks like getopt_long thinks a + option is defined and it "
+ "even requires an argument");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_types);
+ATF_TEST_CASE_BODY(option_types)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2=one", NULL};
+ const string_option flag1("flag1", "The flag1", "arg");
+ const mock_option flag2("flag2");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.has_option("flag1"));
+ ATF_REQUIRE(cmdline.has_option("flag2"));
+ ATF_REQUIRE_EQ("a", cmdline.get_option< string_option >("flag1"));
+ ATF_REQUIRE_EQ(1, cmdline.get_option< mock_option >("flag2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_validation_error);
+ATF_TEST_CASE_BODY(option_validation_error)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=zero", "--flag2=foo",
+ NULL};
+ const mock_option flag1("flag1");
+ const mock_option flag2("flag2");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("option_argument_value_error not raised");
+ } catch (const cmdline::option_argument_value_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ ATF_REQUIRE_EQ("foo", e.argument());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(silent_errors);
+ATF_TEST_CASE_BODY(silent_errors)
+{
+ const int argc = 2;
+ const char* const argv[] = {"progname", "-h", NULL};
+ cmdline::options_vector options;
+
+ try {
+ std::pair< int, int > oldfds = mock_stdfds("output.txt");
+ try {
+ parse(argc, argv, options);
+ } catch (...) {
+ restore_stdfds(oldfds);
+ throw;
+ }
+ restore_stdfds(oldfds);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-h", e.option());
+ }
+
+ std::ifstream input("output.txt");
+ ATF_REQUIRE(input);
+
+ bool has_output = false;
+ std::string line;
+ while (std::getline(input, line).good()) {
+ std::cout << line << '\n';
+ has_output = true;
+ }
+
+ if (has_output)
+ fail("getopt_long printed messages on stdout/stderr by itself");
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, progname__no_options);
+ ATF_ADD_TEST_CASE(tcs, progname__some_options);
+ ATF_ADD_TEST_CASE(tcs, some_args__no_options);
+ ATF_ADD_TEST_CASE(tcs, some_args__some_options);
+ ATF_ADD_TEST_CASE(tcs, some_options__all_known);
+ ATF_ADD_TEST_CASE(tcs, some_options__multi);
+ ATF_ADD_TEST_CASE(tcs, subcommands);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__short);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__shortblock);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__long);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__short);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__shortblock);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__long);
+ ATF_ADD_TEST_CASE(tcs, unknown_plus_option_error);
+ ATF_ADD_TEST_CASE(tcs, option_types);
+ ATF_ADD_TEST_CASE(tcs, option_validation_error);
+ ATF_ADD_TEST_CASE(tcs, silent_errors);
+}
diff --git a/utils/cmdline/ui.cpp b/utils/cmdline/ui.cpp
new file mode 100644
index 000000000000..a682360a4259
--- /dev/null
+++ b/utils/cmdline/ui.cpp
@@ -0,0 +1,276 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/ui.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#if defined(HAVE_TERMIOS_H)
+# include <termios.h>
+#endif
+#include <unistd.h>
+}
+
+#include <iostream>
+
+#include "utils/cmdline/globals.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/operations.ipp"
+#include "utils/text/table.hpp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+using utils::none;
+using utils::optional;
+
+
+/// Destructor for the class.
+cmdline::ui::~ui(void)
+{
+}
+
+
+/// Writes a single line to stderr.
+///
+/// The written line is printed as is, without being wrapped to fit within the
+/// screen width. If the caller wants to print more than one line, it shall
+/// invoke this function once per line.
+///
+/// \param message The line to print. Should not include a trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+cmdline::ui::err(const std::string& message, const bool newline)
+{
+ LI(F("stderr: %s") % message);
+ if (newline)
+ std::cerr << message << "\n";
+ else {
+ std::cerr << message;
+ std::cerr.flush();
+ }
+}
+
+
+/// Writes a single line to stdout.
+///
+/// The written line is printed as is, without being wrapped to fit within the
+/// screen width. If the caller wants to print more than one line, it shall
+/// invoke this function once per line.
+///
+/// \param message The line to print. Should not include a trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+cmdline::ui::out(const std::string& message, const bool newline)
+{
+ LI(F("stdout: %s") % message);
+ if (newline)
+ std::cout << message << "\n";
+ else {
+ std::cout << message;
+ std::cout.flush();
+ }
+}
+
+
+/// Queries the width of the screen.
+///
+/// This information comes first from the COLUMNS environment variable. If not
+/// present or invalid, and if the stdout of the current process is connected to
+/// a terminal the width is deduced from the terminal itself. Ultimately, if
+/// all fails, none is returned. This function shall not raise any errors.
+///
+/// Be aware that the results of this query are cached during execution.
+/// Subsequent calls to this function will always return the same value even if
+/// the terminal size has actually changed.
+///
+/// \todo Install a signal handler for SIGWINCH so that we can readjust our
+/// knowledge of the terminal width when the user resizes the window.
+///
+/// \return The width of the screen if it was possible to determine it, or none
+/// otherwise.
+optional< std::size_t >
+cmdline::ui::screen_width(void) const
+{
+ static bool done = false;
+ static optional< std::size_t > width = none;
+
+ if (!done) {
+ const optional< std::string > columns = utils::getenv("COLUMNS");
+ if (columns) {
+ if (columns.get().length() > 0) {
+ try {
+ width = utils::make_optional(
+ utils::text::to_type< std::size_t >(columns.get()));
+ } catch (const utils::text::value_error& e) {
+ LD(F("Ignoring invalid value in COLUMNS variable: %s") %
+ e.what());
+ }
+ }
+ }
+ if (!width) {
+ struct ::winsize ws;
+ if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
+ width = optional< std::size_t >(ws.ws_col);
+ }
+
+ if (width && width.get() >= 80)
+ width.get() -= 5;
+
+ done = true;
+ }
+
+ return width;
+}
+
+
+/// Writes a line to stdout.
+///
+/// The line is wrapped to fit on screen.
+///
+/// \param message The line to print, without the trailing newline character.
+void
+cmdline::ui::out_wrap(const std::string& message)
+{
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width) {
+ const std::vector< std::string > lines = text::refill(
+ message, max_width.get());
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); iter++)
+ out(*iter);
+ } else
+ out(message);
+}
+
+
+/// Writes a line to stdout with a leading tag.
+///
+/// If the line does not fit on the current screen width, the line is broken
+/// into pieces and the tag is repeated on every line.
+///
+/// \param tag The leading line tag.
+/// \param message The message to be printed, without the trailing newline
+/// character.
+/// \param repeat If true, print the tag on every line; otherwise, indent the
+/// text of all lines to match the width of the tag on the first line.
+void
+cmdline::ui::out_tag_wrap(const std::string& tag, const std::string& message,
+ const bool repeat)
+{
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width && max_width.get() > tag.length()) {
+ const std::vector< std::string > lines = text::refill(
+ message, max_width.get() - tag.length());
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); iter++) {
+ if (repeat || iter == lines.begin())
+ out(F("%s%s") % tag % *iter);
+ else
+ out(F("%s%s") % std::string(tag.length(), ' ') % *iter);
+ }
+ } else {
+ out(F("%s%s") % tag % message);
+ }
+}
+
+
+/// Writes a table to stdout.
+///
+/// \param table The table to write.
+/// \param formatter The table formatter to use to convert the table to a
+/// console representation.
+/// \param prefix Text to prepend to all the lines of the output table.
+void
+cmdline::ui::out_table(const text::table& table,
+ text::table_formatter formatter,
+ const std::string& prefix)
+{
+ if (table.empty())
+ return;
+
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width)
+ formatter.set_table_width(max_width.get() - prefix.length());
+
+ const std::vector< std::string > lines = formatter.format(table);
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); ++iter)
+ out(prefix + *iter);
+}
+
+
+/// Formats and prints an error message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_error(ui* ui_, const std::string& message)
+{
+ LE(message);
+ ui_->err(F("%s: E: %s") % cmdline::progname() % message);
+}
+
+
+/// Formats and prints an informational message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_info(ui* ui_, const std::string& message)
+{
+ LI(message);
+ ui_->err(F("%s: I: %s") % cmdline::progname() % message);
+}
+
+
+/// Formats and prints a warning message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_warning(ui* ui_, const std::string& message)
+{
+ LW(message);
+ ui_->err(F("%s: W: %s") % cmdline::progname() % message);
+}
diff --git a/utils/cmdline/ui.hpp b/utils/cmdline/ui.hpp
new file mode 100644
index 000000000000..433bbe903b03
--- /dev/null
+++ b/utils/cmdline/ui.hpp
@@ -0,0 +1,79 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/ui.hpp
+/// Abstractions and utilities to write formatted messages to the console.
+
+#if !defined(UTILS_CMDLINE_UI_HPP)
+#define UTILS_CMDLINE_UI_HPP
+
+#include "utils/cmdline/ui_fwd.hpp"
+
+#include <cstddef>
+#include <string>
+
+#include "utils/optional_fwd.hpp"
+#include "utils/text/table_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Interface to interact with the CLI.
+///
+/// The main purpose of this class is to substitute direct usages of stdout and
+/// stderr. An instance of this class is passed to every command of a CLI,
+/// which allows unit testing and validation of the interaction with the user.
+///
+/// This class writes directly to stdout and stderr. For testing purposes, see
+/// the utils::cmdline::ui_mock class.
+class ui {
+public:
+ virtual ~ui(void);
+
+ virtual void err(const std::string&, const bool = true);
+ virtual void out(const std::string&, const bool = true);
+ virtual optional< std::size_t > screen_width(void) const;
+
+ void out_wrap(const std::string&);
+ void out_tag_wrap(const std::string&, const std::string&,
+ const bool = true);
+ void out_table(const utils::text::table&, utils::text::table_formatter,
+ const std::string&);
+};
+
+
+void print_error(ui*, const std::string&);
+void print_info(ui*, const std::string&);
+void print_warning(ui*, const std::string&);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_UI_HPP)
diff --git a/utils/cmdline/ui_fwd.hpp b/utils/cmdline/ui_fwd.hpp
new file mode 100644
index 000000000000..4417beb1a8e8
--- /dev/null
+++ b/utils/cmdline/ui_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/ui_fwd.hpp
+/// Forward declarations for utils/cmdline/ui.hpp
+
+#if !defined(UTILS_CMDLINE_UI_FWD_HPP)
+#define UTILS_CMDLINE_UI_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class ui;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_UI_FWD_HPP)
diff --git a/utils/cmdline/ui_mock.cpp b/utils/cmdline/ui_mock.cpp
new file mode 100644
index 000000000000..b77943cf147b
--- /dev/null
+++ b/utils/cmdline/ui_mock.cpp
@@ -0,0 +1,114 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/ui_mock.hpp"
+
+#include <iostream>
+
+#include "utils/optional.ipp"
+
+using utils::cmdline::ui_mock;
+using utils::none;
+using utils::optional;
+
+
+/// Constructs a new mock UI.
+///
+/// \param screen_width_ The width of the screen to use for testing purposes.
+/// Defaults to 0 to prevent uncontrolled wrapping on our tests.
+ui_mock::ui_mock(const std::size_t screen_width_) :
+ _screen_width(screen_width_)
+{
+}
+
+
+/// Writes a line to stderr and records it for further inspection.
+///
+/// \param message The line to print and record, without the trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+ui_mock::err(const std::string& message, const bool newline)
+{
+ if (newline)
+ std::cerr << message << "\n";
+ else {
+ std::cerr << message << "\n";
+ std::cerr.flush();
+ }
+ _err_log.push_back(message);
+}
+
+
+/// Writes a line to stdout and records it for further inspection.
+///
+/// \param message The line to print and record, without the trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+ui_mock::out(const std::string& message, const bool newline)
+{
+ if (newline)
+ std::cout << message << "\n";
+ else {
+ std::cout << message << "\n";
+ std::cout.flush();
+ }
+ _out_log.push_back(message);
+}
+
+
+/// Queries the width of the screen.
+///
+/// \return Always none, as we do not want to depend on line wrapping in our
+/// tests.
+optional< std::size_t >
+ui_mock::screen_width(void) const
+{
+ return _screen_width > 0 ? optional< std::size_t >(_screen_width) : none;
+}
+
+
+/// Gets all the lines written to stderr.
+///
+/// \return The printed lines.
+const std::vector< std::string >&
+ui_mock::err_log(void) const
+{
+ return _err_log;
+}
+
+
+/// Gets all the lines written to stdout.
+///
+/// \return The printed lines.
+const std::vector< std::string >&
+ui_mock::out_log(void) const
+{
+ return _out_log;
+}
diff --git a/utils/cmdline/ui_mock.hpp b/utils/cmdline/ui_mock.hpp
new file mode 100644
index 000000000000..2c37683af7f3
--- /dev/null
+++ b/utils/cmdline/ui_mock.hpp
@@ -0,0 +1,78 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/cmdline/ui_mock.hpp
+/// Provides the utils::cmdline::ui_mock class.
+///
+/// This file is only supposed to be included from test program, never from
+/// production code.
+
+#if !defined(UTILS_CMDLINE_UI_MOCK_HPP)
+#define UTILS_CMDLINE_UI_MOCK_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "utils/cmdline/ui.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Testable interface to interact with the CLI.
+///
+/// This class records all writes to stdout and stderr to allow further
+/// inspection for testing purposes.
+class ui_mock : public ui {
+ /// Fake width of the screen; if 0, represents none.
+ std::size_t _screen_width;
+
+ /// Messages sent to stderr.
+ std::vector< std::string > _err_log;
+
+ /// Messages sent to stdout.
+ std::vector< std::string > _out_log;
+
+public:
+ ui_mock(const std::size_t = 0);
+
+ void err(const std::string&, const bool = true);
+ void out(const std::string&, const bool = true);
+ optional< std::size_t > screen_width(void) const;
+
+ const std::vector< std::string >& err_log(void) const;
+ const std::vector< std::string >& out_log(void) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_UI_MOCK_HPP)
diff --git a/utils/cmdline/ui_test.cpp b/utils/cmdline/ui_test.cpp
new file mode 100644
index 000000000000..92c64baf95a3
--- /dev/null
+++ b/utils/cmdline/ui_test.cpp
@@ -0,0 +1,424 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/cmdline/ui.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#if defined(HAVE_TERMIOS_H)
+# include <termios.h>
+#endif
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/globals.hpp"
+#include "utils/cmdline/ui_mock.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/table.hpp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Reopens stdout as a tty and returns its width.
+///
+/// \return The width of the tty in columns. If the width is wider than 80, the
+/// result is 5 columns narrower to match the screen_width() algorithm.
+static std::size_t
+reopen_stdout(void)
+{
+ const int fd = ::open("/dev/tty", O_WRONLY);
+ if (fd == -1)
+ ATF_SKIP(F("Cannot open tty for test: %s") % ::strerror(errno));
+ struct ::winsize ws;
+ if (::ioctl(fd, TIOCGWINSZ, &ws) == -1)
+ ATF_SKIP(F("Cannot determine size of tty: %s") % ::strerror(errno));
+
+ if (fd != STDOUT_FILENO) {
+ if (::dup2(fd, STDOUT_FILENO) == -1)
+ ATF_SKIP(F("Failed to redirect stdout: %s") % ::strerror(errno));
+ ::close(fd);
+ }
+
+ return ws.ws_col >= 80 ? ws.ws_col - 5 : ws.ws_col;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_set__no_tty)
+{
+ utils::setenv("COLUMNS", "4321");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_set__tty)
+{
+ utils::setenv("COLUMNS", "4321");
+ (void)reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__no_tty)
+{
+ utils::setenv("COLUMNS", "");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__tty)
+{
+ utils::setenv("COLUMNS", "");
+ const std::size_t columns = reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(columns, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__no_tty)
+{
+ utils::setenv("COLUMNS", "foo bar");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__tty)
+{
+ utils::setenv("COLUMNS", "foo bar");
+ const std::size_t columns = reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(columns, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__tty_is_file);
+ATF_TEST_CASE_BODY(ui__screen_width__tty_is_file)
+{
+ utils::unsetenv("COLUMNS");
+ const int fd = ::open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ ATF_REQUIRE(fd != -1);
+ if (fd != STDOUT_FILENO) {
+ ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
+ ::close(fd);
+ }
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__cached);
+ATF_TEST_CASE_BODY(ui__screen_width__cached)
+{
+ cmdline::ui ui;
+
+ utils::setenv("COLUMNS", "100");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+
+ utils::setenv("COLUMNS", "80");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+
+ utils::unsetenv("COLUMNS");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__err);
+ATF_TEST_CASE_BODY(ui__err)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.err("This is a short message");
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.err_log()[0]);
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__err__tolerates_newline);
+ATF_TEST_CASE_BODY(ui__err__tolerates_newline)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.err("This is a short message\n");
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("This is a short message\n", ui.err_log()[0]);
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out);
+ATF_TEST_CASE_BODY(ui__out)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.out("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out__tolerates_newline);
+ATF_TEST_CASE_BODY(ui__out__tolerates_newline)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.out("This is a short message\n");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message\n", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__no_refill);
+ATF_TEST_CASE_BODY(ui__out_wrap__no_refill)
+{
+ cmdline::ui_mock ui(100);
+ ui.out_wrap("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__refill);
+ATF_TEST_CASE_BODY(ui__out_wrap__refill)
+{
+ cmdline::ui_mock ui(16);
+ ui.out_wrap("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ("message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__no_refill);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__no_refill)
+{
+ cmdline::ui_mock ui(100);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__repeat);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__repeat)
+{
+ cmdline::ui_mock ui(32);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ("Some long tag: message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__no_repeat);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__no_repeat)
+{
+ cmdline::ui_mock ui(32);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message", false);
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ(" message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__tag_too_long);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__tag_too_long)
+{
+ cmdline::ui_mock ui(5);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__empty);
+ATF_TEST_CASE_BODY(ui__out_table__empty)
+{
+ const text::table table(3);
+
+ text::table_formatter formatter;
+ formatter.set_separator(" | ");
+ formatter.set_column_width(0, 23);
+ formatter.set_column_width(1, text::table_formatter::width_refill);
+
+ cmdline::ui_mock ui(52);
+ ui.out_table(table, formatter, " ");
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__not_empty);
+ATF_TEST_CASE_BODY(ui__out_table__not_empty)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ text::table_formatter formatter;
+ formatter.set_separator(" | ");
+ formatter.set_column_width(0, 23);
+ formatter.set_column_width(1, text::table_formatter::width_refill);
+
+ cmdline::ui_mock ui(52);
+ ui.out_table(table, formatter, " ");
+ ATF_REQUIRE_EQ(4, ui.out_log().size());
+ ATF_REQUIRE_EQ(" First | Second | Third",
+ ui.out_log()[0]);
+ ATF_REQUIRE_EQ(" Fourth with some text | Fifth with | Sixth foo",
+ ui.out_log()[1]);
+ ATF_REQUIRE_EQ(" | some more | ",
+ ui.out_log()[2]);
+ ATF_REQUIRE_EQ(" | text | ",
+ ui.out_log()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_error);
+ATF_TEST_CASE_BODY(print_error)
+{
+ cmdline::init("error-program");
+ cmdline::ui_mock ui;
+ cmdline::print_error(&ui, "The error.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("error-program: E: The error.", ui.err_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_info);
+ATF_TEST_CASE_BODY(print_info)
+{
+ cmdline::init("info-program");
+ cmdline::ui_mock ui;
+ cmdline::print_info(&ui, "The info.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("info-program: I: The info.", ui.err_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_warning);
+ATF_TEST_CASE_BODY(print_warning)
+{
+ cmdline::init("warning-program");
+ cmdline::ui_mock ui;
+ cmdline::print_warning(&ui, "The warning.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("warning-program: W: The warning.", ui.err_log()[0]);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__tty_is_file);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__cached);
+
+ ATF_ADD_TEST_CASE(tcs, ui__err);
+ ATF_ADD_TEST_CASE(tcs, ui__err__tolerates_newline);
+ ATF_ADD_TEST_CASE(tcs, ui__out);
+ ATF_ADD_TEST_CASE(tcs, ui__out__tolerates_newline);
+
+ ATF_ADD_TEST_CASE(tcs, ui__out_wrap__no_refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_wrap__refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__no_refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__repeat);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__no_repeat);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__tag_too_long);
+ ATF_ADD_TEST_CASE(tcs, ui__out_table__empty);
+ ATF_ADD_TEST_CASE(tcs, ui__out_table__not_empty);
+
+ ATF_ADD_TEST_CASE(tcs, print_error);
+ ATF_ADD_TEST_CASE(tcs, print_info);
+ ATF_ADD_TEST_CASE(tcs, print_warning);
+}
diff --git a/utils/config/Kyuafile b/utils/config/Kyuafile
new file mode 100644
index 000000000000..c607a1757275
--- /dev/null
+++ b/utils/config/Kyuafile
@@ -0,0 +1,10 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="keys_test"}
+atf_test_program{name="lua_module_test"}
+atf_test_program{name="nodes_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="tree_test"}
diff --git a/utils/config/Makefile.am.inc b/utils/config/Makefile.am.inc
new file mode 100644
index 000000000000..7c276ec4e798
--- /dev/null
+++ b/utils/config/Makefile.am.inc
@@ -0,0 +1,87 @@
+# Copyright 2012 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(LUTOK_CFLAGS)
+UTILS_LIBS += $(LUTOK_LIBS)
+
+libutils_a_CPPFLAGS += $(LUTOK_CFLAGS)
+libutils_a_SOURCES += utils/config/exceptions.cpp
+libutils_a_SOURCES += utils/config/exceptions.hpp
+libutils_a_SOURCES += utils/config/keys.cpp
+libutils_a_SOURCES += utils/config/keys.hpp
+libutils_a_SOURCES += utils/config/keys_fwd.hpp
+libutils_a_SOURCES += utils/config/lua_module.cpp
+libutils_a_SOURCES += utils/config/lua_module.hpp
+libutils_a_SOURCES += utils/config/nodes.cpp
+libutils_a_SOURCES += utils/config/nodes.hpp
+libutils_a_SOURCES += utils/config/nodes.ipp
+libutils_a_SOURCES += utils/config/nodes_fwd.hpp
+libutils_a_SOURCES += utils/config/parser.cpp
+libutils_a_SOURCES += utils/config/parser.hpp
+libutils_a_SOURCES += utils/config/parser_fwd.hpp
+libutils_a_SOURCES += utils/config/tree.cpp
+libutils_a_SOURCES += utils/config/tree.hpp
+libutils_a_SOURCES += utils/config/tree.ipp
+libutils_a_SOURCES += utils/config/tree_fwd.hpp
+
+if WITH_ATF
+tests_utils_configdir = $(pkgtestsdir)/utils/config
+
+tests_utils_config_DATA = utils/config/Kyuafile
+EXTRA_DIST += $(tests_utils_config_DATA)
+
+tests_utils_config_PROGRAMS = utils/config/exceptions_test
+utils_config_exceptions_test_SOURCES = utils/config/exceptions_test.cpp
+utils_config_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/keys_test
+utils_config_keys_test_SOURCES = utils/config/keys_test.cpp
+utils_config_keys_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_keys_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/lua_module_test
+utils_config_lua_module_test_SOURCES = utils/config/lua_module_test.cpp
+utils_config_lua_module_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_lua_module_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/nodes_test
+utils_config_nodes_test_SOURCES = utils/config/nodes_test.cpp
+utils_config_nodes_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_nodes_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/parser_test
+utils_config_parser_test_SOURCES = utils/config/parser_test.cpp
+utils_config_parser_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_parser_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/tree_test
+utils_config_tree_test_SOURCES = utils/config/tree_test.cpp
+utils_config_tree_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_tree_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/config/exceptions.cpp b/utils/config/exceptions.cpp
new file mode 100644
index 000000000000..e9afdf7ea6f7
--- /dev/null
+++ b/utils/config/exceptions.cpp
@@ -0,0 +1,149 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/exceptions.hpp"
+
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The key that caused the combination conflict.
+/// \param format The plain-text error message.
+config::bad_combination_error::bad_combination_error(
+ const detail::tree_key& key, const std::string& format) :
+ error(F(format.empty() ? "Combination conflict in key '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::bad_combination_error::~bad_combination_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::invalid_key_error::invalid_key_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_error::~invalid_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param message The plain-text error message.
+config::invalid_key_value::invalid_key_value(const detail::tree_key& key,
+ const std::string& message) :
+ error(F("Invalid value for property '%s': %s")
+ % detail::flatten_key(key) % message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_value::~invalid_key_value(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::syntax_error::syntax_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::syntax_error::~syntax_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param format The message for the error. Must include a single "%s"
+/// placedholder, which will be replaced by the key itself.
+config::unknown_key_error::unknown_key_error(const detail::tree_key& key,
+ const std::string& format) :
+ error(F(format.empty() ? "Unknown configuration property '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::unknown_key_error::~unknown_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::value_error::value_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::value_error::~value_error(void) throw()
+{
+}
diff --git a/utils/config/exceptions.hpp b/utils/config/exceptions.hpp
new file mode 100644
index 000000000000..2096e67f43c8
--- /dev/null
+++ b/utils/config/exceptions.hpp
@@ -0,0 +1,106 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/exceptions.hpp
+/// Exception types raised by the config module.
+
+#if !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
+#define UTILS_CONFIG_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Base exceptions for config errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exception denoting that two trees cannot be combined.
+class bad_combination_error : public error {
+public:
+ explicit bad_combination_error(const detail::tree_key&,
+ const std::string&);
+ ~bad_combination_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class invalid_key_error : public error {
+public:
+ explicit invalid_key_error(const std::string&);
+ ~invalid_key_error(void) throw();
+};
+
+
+/// Exception denoting that a key was given an invalid value.
+class invalid_key_value : public error {
+public:
+ explicit invalid_key_value(const detail::tree_key&, const std::string&);
+ ~invalid_key_value(void) throw();
+};
+
+
+/// Exception denoting that a configuration file is invalid.
+class syntax_error : public error {
+public:
+ explicit syntax_error(const std::string&);
+ ~syntax_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class unknown_key_error : public error {
+public:
+ explicit unknown_key_error(const detail::tree_key&,
+ const std::string& = "");
+ ~unknown_key_error(void) throw();
+};
+
+
+/// Exception denoting that a value was invalid.
+class value_error : public error {
+public:
+ explicit value_error(const std::string&);
+ ~value_error(void) throw();
+};
+
+
+} // namespace config
+} // namespace utils
+
+
+#endif // !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
diff --git a/utils/config/exceptions_test.cpp b/utils/config/exceptions_test.cpp
new file mode 100644
index 000000000000..a82fb9ea8f0c
--- /dev/null
+++ b/utils/config/exceptions_test.cpp
@@ -0,0 +1,133 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const config::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bad_combination_error);
+ATF_TEST_CASE_BODY(bad_combination_error)
+{
+ detail::tree_key key;
+ key.push_back("first");
+ key.push_back("second");
+
+ const config::bad_combination_error e(key, "Failed to combine '%s'");
+ ATF_REQUIRE(std::strcmp("Failed to combine 'first.second'", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_error);
+ATF_TEST_CASE_BODY(invalid_key_error)
+{
+ const config::invalid_key_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_value);
+ATF_TEST_CASE_BODY(invalid_key_value)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::invalid_key_value e(key, "foo bar");
+ ATF_REQUIRE(std::strcmp("Invalid value for property '1.two': foo bar",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_error);
+ATF_TEST_CASE_BODY(syntax_error)
+{
+ const config::syntax_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__default_message);
+ATF_TEST_CASE_BODY(unknown_key_error__default_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key);
+ ATF_REQUIRE(std::strcmp("Unknown configuration property '1.two'",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__custom_message);
+ATF_TEST_CASE_BODY(unknown_key_error__custom_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key, "The test '%s' string");
+ ATF_REQUIRE(std::strcmp("The test '1.two' string", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ const config::value_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, bad_combination_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, syntax_error);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__default_message);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__custom_message);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/keys.cpp b/utils/config/keys.cpp
new file mode 100644
index 000000000000..574eee14dcd2
--- /dev/null
+++ b/utils/config/keys.cpp
@@ -0,0 +1,70 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.hpp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+/// Converts a key to its textual representation.
+///
+/// \param key The key to convert.
+///
+/// \return a flattened representation of \p key, "."-joined.
+std::string
+utils::config::detail::flatten_key(const tree_key& key)
+{
+ PRE(!key.empty());
+ return text::join(key, ".");
+}
+
+
+/// Parses and validates a textual key.
+///
+/// \param str The key to process in dotted notation.
+///
+/// \return The tokenized key if valid.
+///
+/// \throw invalid_key_error If the input key is empty or invalid for any other
+/// reason. Invalid does NOT mean unknown though.
+utils::config::detail::tree_key
+utils::config::detail::parse_key(const std::string& str)
+{
+ const tree_key key = text::split(str, '.');
+ if (key.empty())
+ throw invalid_key_error("Empty key");
+ for (tree_key::const_iterator iter = key.begin(); iter != key.end(); iter++)
+ if ((*iter).empty())
+ throw invalid_key_error(F("Empty component in key '%s'") % str);
+ return key;
+}
diff --git a/utils/config/keys.hpp b/utils/config/keys.hpp
new file mode 100644
index 000000000000..ad258d69fc08
--- /dev/null
+++ b/utils/config/keys.hpp
@@ -0,0 +1,52 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/keys.hpp
+/// Representation and manipulation of tree keys.
+
+#if !defined(UTILS_CONFIG_KEYS_HPP)
+#define UTILS_CONFIG_KEYS_HPP
+
+#include "utils/config/keys_fwd.hpp"
+
+#include <string>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+std::string flatten_key(const tree_key&);
+tree_key parse_key(const std::string&);
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_HPP)
diff --git a/utils/config/keys_fwd.hpp b/utils/config/keys_fwd.hpp
new file mode 100644
index 000000000000..101272698b65
--- /dev/null
+++ b/utils/config/keys_fwd.hpp
@@ -0,0 +1,51 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/keys_fwd.hpp
+/// Forward declarations for utils/config/keys.hpp
+
+#if !defined(UTILS_CONFIG_KEYS_FWD_HPP)
+#define UTILS_CONFIG_KEYS_FWD_HPP
+
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+/// Representation of a valid, tokenized key.
+typedef std::vector< std::string > tree_key;
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_FWD_HPP)
diff --git a/utils/config/keys_test.cpp b/utils/config/keys_test.cpp
new file mode 100644
index 000000000000..dc30f0fc8806
--- /dev/null
+++ b/utils/config/keys_test.cpp
@@ -0,0 +1,114 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/keys.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+
+namespace config = utils::config;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__one);
+ATF_TEST_CASE_BODY(flatten_key__one)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ ATF_REQUIRE_EQ("foo", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__many);
+ATF_TEST_CASE_BODY(flatten_key__many)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ key.push_back("1");
+ key.push_back("bar");
+ ATF_REQUIRE_EQ("foo.1.bar", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__one);
+ATF_TEST_CASE_BODY(parse_key__one)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__many);
+ATF_TEST_CASE_BODY(parse_key__many)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ exp_key.push_back("2");
+ exp_key.push_back("foo");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one.2.foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_key);
+ATF_TEST_CASE_BODY(parse_key__empty_key)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty key",
+ config::detail::parse_key(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_component);
+ATF_TEST_CASE_BODY(parse_key__empty_component)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.'",
+ config::detail::parse_key("."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a.'",
+ config::detail::parse_key("a."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.b'",
+ config::detail::parse_key(".b"));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a..b'",
+ config::detail::parse_key("a..b"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, flatten_key__one);
+ ATF_ADD_TEST_CASE(tcs, flatten_key__many);
+
+ ATF_ADD_TEST_CASE(tcs, parse_key__one);
+ ATF_ADD_TEST_CASE(tcs, parse_key__many);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_key);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_component);
+}
diff --git a/utils/config/lua_module.cpp b/utils/config/lua_module.cpp
new file mode 100644
index 000000000000..891f07302e0a
--- /dev/null
+++ b/utils/config/lua_module.cpp
@@ -0,0 +1,282 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/lua_module.hpp"
+
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+namespace {
+
+
+/// Gets the tree singleton stored in the Lua state.
+///
+/// \param state The Lua state. The registry must contain a key named
+/// "tree" with a pointer to the singleton.
+///
+/// \return A reference to the tree associated with the Lua state.
+///
+/// \throw syntax_error If the tree cannot be located.
+config::tree&
+get_global_tree(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ state.get_table(-2);
+ if (state.is_nil(-1))
+ throw config::syntax_error("Cannot find tree singleton; global state "
+ "corrupted?");
+ config::tree& tree = **state.to_userdata< config::tree* >(-1);
+ state.pop(1);
+ return tree;
+}
+
+
+/// Gets a fully-qualified tree key from the state.
+///
+/// \param state The Lua state.
+/// \param table_index An index to the Lua stack pointing to the table being
+/// accessed. If this table contains a tree_key metadata property, this is
+/// considered to be the prefix of the tree key.
+/// \param field_index An index to the Lua stack pointing to the entry
+/// containing the name of the field being indexed.
+///
+/// \return A dotted key.
+///
+/// \throw invalid_key_error If the name of the key is invalid.
+static std::string
+get_tree_key(lutok::state& state, const int table_index, const int field_index)
+{
+ PRE(state.is_string(field_index));
+ const std::string field = state.to_string(field_index);
+ if (!field.empty() && field[0] == '_')
+ throw config::invalid_key_error(
+ F("Configuration key cannot have an underscore as a prefix; "
+ "found %s") % field);
+
+ std::string tree_key;
+ if (state.get_metafield(table_index, "tree_key")) {
+ tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);
+ state.pop(1);
+ } else
+ tree_key = state.to_string(field_index);
+ return tree_key;
+}
+
+
+static int redirect_newindex(lutok::state&);
+static int redirect_index(lutok::state&);
+
+
+/// Creates a table for a new configuration inner node.
+///
+/// \post state(-1) Contains the new table.
+///
+/// \param state The Lua state in which to push the table.
+/// \param tree_key The key to which the new table corresponds.
+static void
+new_table_for_key(lutok::state& state, const std::string& tree_key)
+{
+ state.new_table();
+ {
+ state.new_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+
+ state.push_string("tree_key");
+ state.push_string(tree_key);
+ state.set_table(-3);
+ }
+ state.set_metatable(-2);
+ }
+}
+
+
+/// Sets the value of an configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level.
+/// \pre state(-2) The field to index into the table. Must be a string.
+/// \pre state(-1) The value to set the indexed table field to.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 0.
+///
+/// \throw invalid_key_error If the provided key is invalid.
+/// \throw unknown_key_error If the key cannot be located.
+/// \throw value_error If the value has an unsupported type or cannot be
+/// set on the key, or if the input table or index are invalid.
+static int
+redirect_newindex(lutok::state& state)
+{
+ if (!state.is_table(-3))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-2))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ const std::string dotted_key = get_tree_key(state, -3, -2);
+ try {
+ config::tree& tree = get_global_tree(state);
+ tree.set_lua(dotted_key, state, -1);
+ } catch (const config::value_error& e) {
+ throw config::invalid_key_value(detail::parse_key(dotted_key),
+ e.what());
+ }
+
+ // Now really set the key in the Lua table, but prevent direct accesses from
+ // the user by prefixing it. We do this to ensure that re-setting the same
+ // key of the tree results in a call to __newindex instead of __index.
+ state.push_string("_" + state.to_string(-2));
+ state.push_value(-2);
+ state.raw_set(-5);
+
+ return 0;
+}
+
+
+/// Indexes a configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level. If the field does not exist, a new table is created.
+/// \pre state(-1) The field to index into the table. Must be a string.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 1.
+///
+/// \throw value_error If the input table or index are invalid.
+static int
+redirect_index(lutok::state& state)
+{
+ if (!state.is_table(-2))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-1))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ // Query if the key has already been set by a call to redirect_newindex.
+ state.push_string("_" + state.to_string(-1));
+ state.raw_get(-3);
+ if (!state.is_nil(-1))
+ return 1;
+ state.pop(1);
+
+ state.push_value(-1); // Duplicate the field name.
+ state.raw_get(-3); // Get table[field] to see if it's defined.
+ if (state.is_nil(-1)) {
+ state.pop(1);
+
+ // The stack is now the same as when we entered the function, but we
+ // know that the field is undefined and thus have to create a new
+ // configuration table.
+ INV(state.is_table(-2));
+ INV(state.is_string(-1));
+
+ const config::tree& tree = get_global_tree(state);
+ const std::string tree_key = get_tree_key(state, -2, -1);
+ if (tree.is_set(tree_key)) {
+ // Publish the pre-recorded value in the tree to the Lua state,
+ // instead of considering this table key a new inner node.
+ tree.push_lua(tree_key, state);
+ } else {
+ state.push_string("_" + state.to_string(-1));
+ state.insert(-2);
+ state.pop(1);
+
+ new_table_for_key(state, tree_key);
+
+ // Duplicate the newly created table and place it deep in the stack
+ // so that the raw_set below leaves us with the return value of this
+ // function at the top of the stack.
+ state.push_value(-1);
+ state.insert(-4);
+
+ state.raw_set(-3);
+ state.pop(1);
+ }
+ }
+ return 1;
+}
+
+
+} // anonymous namespace
+
+
+/// Install wrappers for globals to set values in the configuration tree.
+///
+/// This function installs wrappers to capture all accesses to global variables.
+/// Such wrappers redirect the reads and writes to the out_tree, which is the
+/// entity that defines what configuration variables exist.
+///
+/// \param state The Lua state into which to install the wrappers.
+/// \param out_tree The tree with the layout definition and where the
+/// configuration settings will be collected.
+void
+config::redirect(lutok::state& state, tree& out_tree)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.get_global_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+ }
+ state.set_metatable(-1);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ config::tree** tree = state.new_userdata< config::tree* >();
+ *tree = &out_tree;
+ state.set_table(-3);
+ state.pop(1);
+}
diff --git a/utils/config/lua_module.hpp b/utils/config/lua_module.hpp
new file mode 100644
index 000000000000..7f0d5d0b4c5f
--- /dev/null
+++ b/utils/config/lua_module.hpp
@@ -0,0 +1,50 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/lua_module.hpp
+/// Bindings to expose a configuration tree to Lua.
+
+#if !defined(UTILS_CONFIG_LUA_MODULE_HPP)
+#define UTILS_CONFIG_LUA_MODULE_HPP
+
+#include <string>
+
+#include "lutok/state.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+void redirect(lutok::state&, tree&);
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_LUA_MODULE_HPP)
diff --git a/utils/config/lua_module_test.cpp b/utils/config/lua_module_test.cpp
new file mode 100644
index 000000000000..484d129c4021
--- /dev/null
+++ b/utils/config/lua_module_test.cpp
@@ -0,0 +1,474 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/lua_module.hpp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/tree.ipp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Non-native type to use as a leaf node.
+struct custom_type {
+ /// The value recorded in the object.
+ int value;
+
+ /// Constructs a new object.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit custom_type(const int value_) :
+ value(value_)
+ {
+ }
+};
+
+
+/// Custom implementation of a node type for testing purposes.
+class custom_node : public config::typed_leaf_node< custom_type > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< custom_node > new_node(new custom_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(value().value * 5);
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ set(custom_type(state.to_integer(value_index) * 2));
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ void
+ set_string(const std::string& /* raw_value */)
+ {
+ ATF_FAIL("Should not be used");
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ ///
+ /// \return Nothing.
+ std::string
+ to_string(void) const
+ {
+ ATF_FAIL("Should not be used");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__valid_types);
+ATF_TEST_CASE_BODY(top__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+ tree.define< config::string_node >("top_string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 12345\n"
+ "top_string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("top_integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("top_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__invalid_types);
+ATF_TEST_CASE_BODY(top__invalid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(
+ lutok::error,
+ "Invalid value for property 'top_boolean': Not a boolean",
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 8\n"
+ "top_boolean = 'foo'\n",
+ 0, 0, 0));
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(8, tree.lookup< config::int_node >("top_integer"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reuse);
+ATF_TEST_CASE_BODY(top__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.define< config::int_node >("second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; second = first * 2", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reset);
+ATF_TEST_CASE_BODY(top__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__already_set_on_entry);
+ATF_TEST_CASE_BODY(top__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.set< config::int_node >("first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__valid_types);
+ATF_TEST_CASE_BODY(subtree__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("root.boolean");
+ tree.define< config::int_node >("root.a.integer");
+ tree.define< config::string_node >("root.string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.boolean = true\n"
+ "root.a.integer = 12345\n"
+ "root.string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("root.boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("root.a.integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("root.string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reuse);
+ATF_TEST_CASE_BODY(subtree__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.define< config::int_node >("a.second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.second = a.first * 2",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("a.first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reset);
+ATF_TEST_CASE_BODY(subtree__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__already_set_on_entry);
+ATF_TEST_CASE_BODY(subtree__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.set< config::int_node >("a.first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = a.first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__override_inner);
+ATF_TEST_CASE_BODY(subtree__override_inner)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "root.test = 'a'", 0, 0, 0);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root'",
+ lutok::do_string(state, "root = 'b'", 0, 0, 0));
+ // Ensure that the previous assignment to 'root' did not cause any
+ // inconsistencies in the environment that would prevent a new
+ // assignment from working.
+ lutok::do_string(state, "root.test2 = 'c'", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ("a", tree.lookup< config::string_node >("root.test"));
+ ATF_REQUIRE_EQ("c", tree.lookup< config::string_node >("root.test2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__strings);
+ATF_TEST_CASE_BODY(dynamic_subtree__strings)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.key1 = 1234\n"
+ "root.a.b.key2 = 'foo bar'\n",
+ 0, 0, 0);
+
+ ATF_REQUIRE_EQ("1234", tree.lookup< config::string_node >("root.key1"));
+ ATF_REQUIRE_EQ("foo bar",
+ tree.lookup< config::string_node >("root.a.b.key2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__invalid_types);
+ATF_TEST_CASE_BODY(dynamic_subtree__invalid_types)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.boolean': "
+ "Not a string",
+ lutok::do_string(state, "root.boolean = true",
+ 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.table': "
+ "Not a string",
+ lutok::do_string(state, "root.table = {}",
+ 0, 0, 0));
+ ATF_REQUIRE(!tree.is_set("root.boolean"));
+ ATF_REQUIRE(!tree.is_set("root.table"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(locals);
+ATF_TEST_CASE_BODY(locals)
+{
+ config::tree tree;
+ tree.define< config::int_node >("the_key");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "local function generate()\n"
+ " return 15\n"
+ "end\n"
+ "local test_var = 20\n"
+ "the_key = generate() + test_var\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(35, tree.lookup< config::int_node >("the_key"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_node);
+ATF_TEST_CASE_BODY(custom_node)
+{
+ config::tree tree;
+ tree.define< custom_node >("key1");
+ tree.define< custom_node >("key2");
+ tree.set< custom_node >("key2", custom_type(10));
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "key1 = 512\n", 0, 0, 0);
+ lutok::do_string(state, "key2 = key2 * 2\n", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1024, tree.lookup< custom_node >("key1").value);
+ ATF_REQUIRE_EQ(200, tree.lookup< custom_node >("key2").value);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key);
+ATF_TEST_CASE_BODY(invalid_key)
+{
+ config::tree tree;
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Empty component in key 'root.'",
+ lutok::do_string(state, "root['']['a'] = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key);
+ATF_TEST_CASE_BODY(unknown_key)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("static.bool");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Unknown configuration property 'static.int'",
+ lutok::do_string(state,
+ "static.int = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("a.b");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a.b': Not a boolean",
+ lutok::do_string(state, "a.b = 12345\n", 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a': ",
+ lutok::do_string(state, "a = 1\n", 0, 0, 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, top__valid_types);
+ ATF_ADD_TEST_CASE(tcs, top__invalid_types);
+ ATF_ADD_TEST_CASE(tcs, top__reuse);
+ ATF_ADD_TEST_CASE(tcs, top__reset);
+ ATF_ADD_TEST_CASE(tcs, top__already_set_on_entry);
+
+ ATF_ADD_TEST_CASE(tcs, subtree__valid_types);
+ ATF_ADD_TEST_CASE(tcs, subtree__reuse);
+ ATF_ADD_TEST_CASE(tcs, subtree__reset);
+ ATF_ADD_TEST_CASE(tcs, subtree__already_set_on_entry);
+ ATF_ADD_TEST_CASE(tcs, subtree__override_inner);
+
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__strings);
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__invalid_types);
+
+ ATF_ADD_TEST_CASE(tcs, locals);
+ ATF_ADD_TEST_CASE(tcs, custom_node);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_key);
+ ATF_ADD_TEST_CASE(tcs, unknown_key);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/nodes.cpp b/utils/config/nodes.cpp
new file mode 100644
index 000000000000..1c6e848daf07
--- /dev/null
+++ b/utils/config/nodes.cpp
@@ -0,0 +1,589 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.ipp"
+
+#include <memory>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Destructor.
+config::detail::base_node::~base_node(void)
+{
+}
+
+
+/// Constructor.
+///
+/// \param dynamic_ Whether the node is dynamic or not.
+config::detail::inner_node::inner_node(const bool dynamic_) :
+ _dynamic(dynamic_)
+{
+}
+
+
+/// Destructor.
+config::detail::inner_node::~inner_node(void)
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter)
+ delete (*iter).second;
+}
+
+
+/// Fills the given node with a copy of this node's data.
+///
+/// \param node The node to fill. Should be the fresh return value of a
+/// deep_copy() operation.
+void
+config::detail::inner_node::copy_into(inner_node* node) const
+{
+ node->_dynamic = _dynamic;
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ base_node* new_node = (*iter).second->deep_copy();
+ try {
+ node->_children[(*iter).first] = new_node;
+ } catch (...) {
+ delete new_node;
+ throw;
+ }
+ }
+}
+
+
+/// Combines two children sets, preferring the keys in the first set only.
+///
+/// This operation is not symmetrical on c1 and c2. The caller is responsible
+/// for invoking this twice so that the two key sets are combined if they happen
+/// to differ.
+///
+/// \param key Key to this node.
+/// \param c1 First children set.
+/// \param c2 First children set.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_children_into(
+ const tree_key& key,
+ const children_map& c1, const children_map& c2,
+ inner_node* node) const
+{
+ for (children_map::const_iterator iter1 = c1.begin();
+ iter1 != c1.end(); ++iter1) {
+ const std::string& name = (*iter1).first;
+
+ if (node->_children.find(name) != node->_children.end()) {
+ continue;
+ }
+
+ std::auto_ptr< base_node > new_node;
+
+ children_map::const_iterator iter2 = c2.find(name);
+ if (iter2 == c2.end()) {
+ new_node.reset((*iter1).second->deep_copy());
+ } else {
+ tree_key child_key = key;
+ child_key.push_back(name);
+ new_node.reset((*iter1).second->combine(child_key,
+ (*iter2).second));
+ }
+
+ node->_children[name] = new_node.release();
+ }
+}
+
+
+/// Combines this inner node with another inner node onto a new node.
+///
+/// The "dynamic" property is inherited by the new node if either of the two
+/// nodes are dynamic.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_into(const tree_key& key,
+ const base_node* other_base,
+ inner_node* node) const
+{
+ try {
+ const inner_node& other = dynamic_cast< const inner_node& >(
+ *other_base);
+
+ node->_dynamic = _dynamic || other._dynamic;
+
+ combine_children_into(key, _children, other._children, node);
+ combine_children_into(key, other._children, _children, node);
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is an inner node in the base tree but a leaf node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Finds a node without creating it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key.
+/// The returned node is constant, so this can only be used for querying
+/// purposes. For this reason, this algorithm does not create intermediate
+/// nodes if they don't exist (as would be necessary to set a new node).
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw unknown_key_error If the provided key is unknown.
+const config::detail::base_node*
+config::detail::inner_node::lookup_ro(const tree_key& key,
+ const tree_key::size_type key_pos) const
+{
+ PRE(key_pos < key.size());
+
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+ if (child_iter == _children.end())
+ throw unknown_key_error(key);
+
+ if (key_pos == key.size() - 1) {
+ return (*child_iter).second;
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ const inner_node& child = dynamic_cast< const inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_ro(key, key_pos + 1);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Finds a node and creates it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key,
+/// creating any intermediate nodes if they do not already exist (for the case
+/// of dynamic inner nodes). The returned node is non-constant, so this can be
+/// used by the algorithms that set key values.
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type. This is only called if the leaf cannot be found, but it has
+/// already been defined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw invalid_key_value If the resulting node of the search would be an
+/// inner node.
+/// \throw unknown_key_error If the provided key is unknown.
+config::leaf_node*
+config::detail::inner_node::lookup_rw(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ children_map::const_iterator child_iter = _children.find(key[key_pos]);
+ if (child_iter == _children.end()) {
+ if (_dynamic) {
+ base_node* const child = (key_pos == key.size() - 1) ?
+ static_cast< base_node* >(new_node()) :
+ static_cast< base_node* >(new dynamic_inner_node());
+ _children.insert(children_map::value_type(key[key_pos], child));
+ child_iter = _children.find(key[key_pos]);
+ } else {
+ throw unknown_key_error(key);
+ }
+ }
+
+ if (key_pos == key.size() - 1) {
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(
+ *(*child_iter).second);
+ return &child;
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ inner_node& child = dynamic_cast< inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_rw(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Converts the subtree to a collection of key/value string pairs.
+///
+/// \param [out] properties The accumulator for the generated properties. The
+/// contents of the map are only extended.
+/// \param key The path to the current node.
+void
+config::detail::inner_node::all_properties(properties_map& properties,
+ const tree_key& key) const
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ tree_key child_key = key;
+ child_key.push_back((*iter).first);
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(*(*iter).second);
+ if (child.is_set())
+ properties[flatten_key(child_key)] = child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ inner_node& child = dynamic_cast< inner_node& >(*(*iter).second);
+ child.all_properties(properties, child_key);
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::static_inner_node::static_inner_node(void) :
+ inner_node(false)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::static_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::static_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param key The key to be registered.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type.
+void
+config::detail::static_inner_node::define(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ if (key_pos == key.size() - 1) {
+ PRE_MSG(_children.find(key[key_pos]) == _children.end(),
+ "Key already defined");
+ _children.insert(children_map::value_type(key[key_pos], new_node()));
+ } else {
+ PRE(key_pos < key.size() - 1);
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+
+ if (child_iter == _children.end()) {
+ static_inner_node* const child_ptr = new static_inner_node();
+ _children.insert(children_map::value_type(key[key_pos], child_ptr));
+ child_ptr->define(key, key_pos + 1, new_node);
+ } else {
+ try {
+ static_inner_node& child = dynamic_cast< static_inner_node& >(
+ *(*child_iter).second);
+ child.define(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ UNREACHABLE;
+ }
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::dynamic_inner_node::dynamic_inner_node(void) :
+ inner_node(true)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::dynamic_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::dynamic_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Destructor.
+config::leaf_node::~leaf_node(void)
+{
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::leaf_node::combine(const detail::tree_key& key,
+ const base_node* other_base) const
+{
+ try {
+ const leaf_node& other = dynamic_cast< const leaf_node& >(*other_base);
+
+ if (other.is_set()) {
+ return other.deep_copy();
+ } else {
+ return deep_copy();
+ }
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is a leaf node in the base tree but an inner node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::bool_node::deep_copy(void) const
+{
+ std::auto_ptr< bool_node > new_node(new bool_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::bool_node::push_lua(lutok::state& state) const
+{
+ state.push_boolean(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::bool_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_boolean(value_index))
+ set(state.to_boolean(value_index));
+ else
+ throw value_error("Not a boolean");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::int_node::deep_copy(void) const
+{
+ std::auto_ptr< int_node > new_node(new int_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::int_node::push_lua(lutok::state& state) const
+{
+ state.push_integer(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::int_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_number(value_index))
+ set(state.to_integer(value_index));
+ else
+ throw value_error("Not an integer");
+}
+
+
+/// Checks a given value for validity.
+///
+/// \param new_value The value to validate.
+///
+/// \throw value_error If the value is not valid.
+void
+config::positive_int_node::validate(const value_type& new_value) const
+{
+ if (new_value <= 0)
+ throw value_error("Must be a positive integer");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::string_node::deep_copy(void) const
+{
+ std::auto_ptr< string_node > new_node(new string_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::string_node::push_lua(lutok::state& state) const
+{
+ state.push_string(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::string_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_string(value_index))
+ set(state.to_string(value_index));
+ else
+ throw value_error("Not a string");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::strings_set_node::deep_copy(void) const
+{
+ std::auto_ptr< strings_set_node > new_node(new strings_set_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Converts a single word to the native type.
+///
+/// \param raw_value The value to parse.
+///
+/// \return The parsed value.
+std::string
+config::strings_set_node::parse_one(const std::string& raw_value) const
+{
+ return raw_value;
+}
diff --git a/utils/config/nodes.hpp b/utils/config/nodes.hpp
new file mode 100644
index 000000000000..6b766ff5d8f7
--- /dev/null
+++ b/utils/config/nodes.hpp
@@ -0,0 +1,272 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/nodes.hpp
+/// Representation of tree nodes.
+
+#if !defined(UTILS_CONFIG_NODES_HPP)
+#define UTILS_CONFIG_NODES_HPP
+
+#include "utils/config/nodes_fwd.hpp"
+
+#include <set>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.hpp"
+
+namespace utils {
+namespace config {
+
+
+namespace detail {
+
+
+/// Base representation of a node.
+///
+/// This abstract class provides the base type for every node in the tree. Due
+/// to the dynamic nature of our trees (each leaf being able to hold arbitrary
+/// data types), this base type is a necessity.
+class base_node : noncopyable {
+public:
+ virtual ~base_node(void) = 0;
+
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node* deep_copy(void) const = 0;
+
+ /// Combines this node with another one.
+ ///
+ /// \param key Key to this node.
+ /// \param other The node to combine with.
+ ///
+ /// \return A new node representing the combination.
+ ///
+ /// \throw bad_combination_error If the two nodes cannot be combined.
+ virtual base_node* combine(const tree_key& key, const base_node* other)
+ const = 0;
+};
+
+
+} // namespace detail
+
+
+/// Abstract leaf node without any specified type.
+///
+/// This base abstract type is necessary to have a common pointer type to which
+/// to cast any leaf. We later provide templated derivates of this class, and
+/// those cannot act in this manner.
+///
+/// It is important to understand that a leaf can exist without actually holding
+/// a value. Our trees are "strictly keyed": keys must have been pre-defined
+/// before a value can be set on them. This is to ensure that the end user is
+/// using valid key names and not making mistakes due to typos, for example. To
+/// represent this condition, we define an "empty" key in the tree to denote
+/// that the key is valid, yet it has not been set by the user. Only when an
+/// explicit set is performed on the key, it gets a value.
+class leaf_node : public detail::base_node {
+public:
+ virtual ~leaf_node(void);
+
+ virtual bool is_set(void) const = 0;
+
+ base_node* combine(const detail::tree_key&, const base_node*) const;
+
+ virtual void push_lua(lutok::state&) const = 0;
+ virtual void set_lua(lutok::state&, const int) = 0;
+
+ virtual void set_string(const std::string&) = 0;
+ virtual std::string to_string(void) const = 0;
+};
+
+
+/// Base leaf node for a single arbitrary type.
+///
+/// This templated leaf node holds a single object of any type. The conversion
+/// to/from string representations is undefined, as that depends on the
+/// particular type being processed. You should reimplement this class for any
+/// type that needs additional processing/validation during conversion.
+template< typename ValueType >
+class typed_leaf_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef ValueType value_type;
+
+ /// Constructs a new leaf node that contains no value.
+ typed_leaf_node(void);
+
+ /// Checks whether the node has been set by the user.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ virtual void validate(const value_type&) const;
+};
+
+
+/// Leaf node holding a native type.
+///
+/// This templated leaf node holds a native type. The conversion to/from string
+/// representations of the value happens by means of iostreams.
+template< typename ValueType >
+class native_leaf_node : public typed_leaf_node< ValueType > {
+public:
+ void set_string(const std::string&);
+ std::string to_string(void) const;
+};
+
+
+/// A leaf node that holds a boolean value.
+class bool_node : public native_leaf_node< bool > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds an integer value.
+class int_node : public native_leaf_node< int > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds a positive non-zero integer value.
+class positive_int_node : public int_node {
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a string value.
+class string_node : public native_leaf_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// Base leaf node for a set of native types.
+///
+/// This is a base abstract class because there is no generic way to parse a
+/// single word in the textual representation of the set to the native value.
+template< typename ValueType >
+class base_set_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef std::set< ValueType > value_type;
+
+ base_set_node(void);
+
+ /// Checks whether the node has been set by the user.
+ ///
+ /// \return True if a value has been set in the node.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+ /// Sets the value of the node from a raw string representation.
+ void set_string(const std::string&);
+
+ /// Converts the contents of the node to a string.
+ std::string to_string(void) const;
+
+ /// Pushes the node's value onto the Lua stack.
+ void push_lua(lutok::state&) const;
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ void set_lua(lutok::state&, const int);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ /// Converts a single word to the native type.
+ ///
+ /// \return The parsed value.
+ ///
+ /// \throw value_error If the value is invalid.
+ virtual ValueType parse_one(const std::string&) const = 0;
+
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a set of strings.
+class strings_set_node : public base_set_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+private:
+ std::string parse_one(const std::string&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_HPP)
diff --git a/utils/config/nodes.ipp b/utils/config/nodes.ipp
new file mode 100644
index 000000000000..9e0a1228cccd
--- /dev/null
+++ b/utils/config/nodes.ipp
@@ -0,0 +1,408 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.hpp"
+
+#if !defined(UTILS_CONFIG_NODES_IPP)
+#define UTILS_CONFIG_NODES_IPP
+
+#include <memory>
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+namespace config {
+namespace detail {
+
+
+/// Type of the new_node() family of functions.
+typedef base_node* (*new_node_hook)(void);
+
+
+/// Creates a new leaf node of a given type.
+///
+/// \tparam NodeType The type of the leaf node to create.
+///
+/// \return A pointer to the newly-created node.
+template< class NodeType >
+base_node*
+new_node(void)
+{
+ return new NodeType();
+}
+
+
+/// Internal node of the tree.
+///
+/// This abstract base class provides the mechanism to implement both static and
+/// dynamic nodes. Ideally, the implementation would be split in subclasses and
+/// this class would not include the knowledge of whether the node is dynamic or
+/// not. However, because the static/dynamic difference depends on the leaf
+/// types, we need to declare template functions and these cannot be virtual.
+class inner_node : public base_node {
+ /// Whether the node is dynamic or not.
+ bool _dynamic;
+
+protected:
+ /// Type to represent the collection of children of this node.
+ ///
+ /// Note that these are one-level keys. They cannot contain dots, and thus
+ /// is why we use a string rather than a tree_key.
+ typedef std::map< std::string, base_node* > children_map;
+
+ /// Mapping of keys to values that are descendants of this node.
+ children_map _children;
+
+ void copy_into(inner_node*) const;
+ void combine_into(const tree_key&, const base_node*, inner_node*) const;
+
+private:
+ void combine_children_into(const tree_key&,
+ const children_map&, const children_map&,
+ inner_node*) const;
+
+public:
+ inner_node(const bool);
+ virtual ~inner_node(void) = 0;
+
+ const base_node* lookup_ro(const tree_key&,
+ const tree_key::size_type) const;
+ leaf_node* lookup_rw(const tree_key&, const tree_key::size_type,
+ new_node_hook);
+
+ void all_properties(properties_map&, const tree_key&) const;
+};
+
+
+/// Static internal node of the tree.
+///
+/// The direct children of this node must be pre-defined by calls to define().
+/// Attempts to traverse this node and resolve a key that is not a pre-defined
+/// children will result in an "unknown key" error.
+class static_inner_node : public config::detail::inner_node {
+public:
+ static_inner_node(void);
+
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ void define(const tree_key&, const tree_key::size_type, new_node_hook);
+};
+
+
+/// Dynamic internal node of the tree.
+///
+/// The children of this node need not be pre-defined. Attempts to traverse
+/// this node and resolve a key will result in such key being created. Any
+/// intermediate non-existent nodes of the traversal will be created as dynamic
+/// inner nodes as well.
+class dynamic_inner_node : public config::detail::inner_node {
+public:
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ dynamic_inner_node(void);
+};
+
+
+} // namespace detail
+} // namespace config
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::typed_leaf_node< ValueType >::typed_leaf_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set by the user.
+///
+/// Nodes of the tree are predefined by the caller to specify the valid
+/// types of the leaves. Such predefinition results in the creation of
+/// nodes within the tree, but these nodes have not yet been set.
+/// Traversing these nodes is invalid and should result in an "unknown key"
+/// error.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::typed_leaf_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::native_leaf_node< ValueType >::set_string(const std::string& raw_value)
+{
+ try {
+ typed_leaf_node< ValueType >::set(text::to_type< ValueType >(
+ raw_value));
+ } catch (const text::value_error& e) {
+ throw config::value_error(F("Failed to convert string value '%s' to "
+ "the node's type") % raw_value);
+ }
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::native_leaf_node< ValueType >::to_string(void) const
+{
+ PRE(typed_leaf_node< ValueType >::is_set());
+ return F("%s") % typed_leaf_node< ValueType >::value();
+}
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::base_set_node< ValueType >::base_set_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set.
+///
+/// Remember that a node can exist before holding a value (i.e. when the node
+/// has been defined as "known" but not yet set by the user). This function
+/// checks whether the node laready holds a value.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::base_set_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_string(const std::string& raw_value)
+{
+ std::set< ValueType > new_value;
+
+ const std::vector< std::string > words = text::split(raw_value, ' ');
+ for (std::vector< std::string >::const_iterator iter = words.begin();
+ iter != words.end(); ++iter) {
+ if (!(*iter).empty())
+ new_value.insert(parse_one(*iter));
+ }
+
+ set(new_value);
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::base_set_node< ValueType >::to_string(void) const
+{
+ PRE(is_set());
+ return text::join(_value.get(), " ");
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::push_lua(lutok::state& /* state */) const
+{
+ UNREACHABLE;
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_lua(
+ lutok::state& /* state */,
+ const int /* value_index */)
+{
+ UNREACHABLE;
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_IPP)
diff --git a/utils/config/nodes_fwd.hpp b/utils/config/nodes_fwd.hpp
new file mode 100644
index 000000000000..b03328e79e95
--- /dev/null
+++ b/utils/config/nodes_fwd.hpp
@@ -0,0 +1,70 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/nodes_fwd.hpp
+/// Forward declarations for utils/config/nodes.hpp
+
+#if !defined(UTILS_CONFIG_NODES_FWD_HPP)
+#define UTILS_CONFIG_NODES_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+namespace detail {
+
+
+class base_node;
+class static_inner_node;
+
+
+} // namespace detail
+
+
+class leaf_node;
+template< typename > class typed_leaf_node;
+template< typename > class native_leaf_node;
+class bool_node;
+class int_node;
+class positive_int_node;
+class string_node;
+template< typename > class base_set_node;
+class strings_set_node;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_FWD_HPP)
diff --git a/utils/config/nodes_test.cpp b/utils/config/nodes_test.cpp
new file mode 100644
index 000000000000..e762d3aac38c
--- /dev/null
+++ b/utils/config/nodes_test.cpp
@@ -0,0 +1,695 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.ipp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Typed leaf node that specializes the validate() method.
+class validation_node : public config::int_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ if (new_value == 12345)
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+/// Set node that specializes the validate() method.
+class set_validation_node : public config::strings_set_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ for (value_type::const_iterator iter = new_value.begin();
+ iter != new_value.end(); ++iter)
+ if (*iter == "throw")
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__deep_copy);
+ATF_TEST_CASE_BODY(bool_node__deep_copy)
+{
+ config::bool_node node;
+ node.set(true);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::bool_node* copy = static_cast< config::bool_node* >(raw_copy);
+ ATF_REQUIRE(copy->value());
+ copy->set(false);
+ ATF_REQUIRE(node.value());
+ ATF_REQUIRE(!copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__is_set_and_set);
+ATF_TEST_CASE_BODY(bool_node__is_set_and_set)
+{
+ config::bool_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(false);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__value_and_set);
+ATF_TEST_CASE_BODY(bool_node__value_and_set)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE(!node.value());
+ node.set(true);
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__push_lua);
+ATF_TEST_CASE_BODY(bool_node__push_lua)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ node.set(true);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_boolean(-1));
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__ok);
+ATF_TEST_CASE_BODY(bool_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_boolean(false);
+ node.set_lua(state, -1);
+ state.pop(1);
+ ATF_REQUIRE(!node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_string("foo bar");
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__ok);
+ATF_TEST_CASE_BODY(bool_node__set_string__ok)
+{
+ config::bool_node node;
+ node.set_string("false");
+ ATF_REQUIRE(!node.value());
+ node.set_string("true");
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_string__invalid_value)
+{
+ config::bool_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("12345"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__to_string);
+ATF_TEST_CASE_BODY(bool_node__to_string)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE_EQ("false", node.to_string());
+ node.set(true);
+ ATF_REQUIRE_EQ("true", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__deep_copy);
+ATF_TEST_CASE_BODY(int_node__deep_copy)
+{
+ config::int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::int_node* copy = static_cast< config::int_node* >(raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(int_node__is_set_and_set)
+{
+ config::int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__value_and_set);
+ATF_TEST_CASE_BODY(int_node__value_and_set)
+{
+ config::int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(0);
+ ATF_REQUIRE_EQ(0, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__push_lua);
+ATF_TEST_CASE_BODY(int_node__push_lua)
+{
+ lutok::state state;
+
+ config::int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__ok);
+ATF_TEST_CASE_BODY(int_node__set_string__ok)
+{
+ config::int_node node;
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+ node.set_string("-123");
+ ATF_REQUIRE_EQ(-123, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_string__invalid_value)
+{
+ config::int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__to_string);
+ATF_TEST_CASE_BODY(int_node__to_string)
+{
+ config::int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+ node.set(-57);
+ ATF_REQUIRE_EQ("-57", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__deep_copy);
+ATF_TEST_CASE_BODY(positive_int_node__deep_copy)
+{
+ config::positive_int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::positive_int_node* copy = static_cast< config::positive_int_node* >(
+ raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__is_set_and_set)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__value_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__value_and_set)
+{
+ config::positive_int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(1);
+ ATF_REQUIRE_EQ(1, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__push_lua);
+ATF_TEST_CASE_BODY(positive_int_node__push_lua)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+ state.push_integer(0);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__ok)
+{
+ config::positive_int_node node;
+ node.set_string("1");
+ ATF_REQUIRE_EQ(1, node.value());
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__invalid_value)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("0"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("-5"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__to_string);
+ATF_TEST_CASE_BODY(positive_int_node__to_string)
+{
+ config::positive_int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__deep_copy);
+ATF_TEST_CASE_BODY(string_node__deep_copy)
+{
+ config::string_node node;
+ node.set("first");
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::string_node* copy = static_cast< config::string_node* >(raw_copy);
+ ATF_REQUIRE_EQ("first", copy->value());
+ copy->set("second");
+ ATF_REQUIRE_EQ("first", node.value());
+ ATF_REQUIRE_EQ("second", copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__is_set_and_set);
+ATF_TEST_CASE_BODY(string_node__is_set_and_set)
+{
+ config::string_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set("foo");
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__value_and_set);
+ATF_TEST_CASE_BODY(string_node__value_and_set)
+{
+ config::string_node node;
+ node.set("foo");
+ ATF_REQUIRE_EQ("foo", node.value());
+ node.set("");
+ ATF_REQUIRE_EQ("", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__push_lua);
+ATF_TEST_CASE_BODY(string_node__push_lua)
+{
+ lutok::state state;
+
+ config::string_node node;
+ node.set("some message");
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("some message", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__ok);
+ATF_TEST_CASE_BODY(string_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::string_node node;
+ state.push_string("text 1");
+ state.push_integer(231);
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ("text 1", node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ("231", node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(string_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.new_table();
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_string);
+ATF_TEST_CASE_BODY(string_node__set_string)
+{
+ config::string_node node;
+ node.set_string("abcd efgh");
+ ATF_REQUIRE_EQ("abcd efgh", node.value());
+ node.set_string(" 1234 ");
+ ATF_REQUIRE_EQ(" 1234 ", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__to_string);
+ATF_TEST_CASE_BODY(string_node__to_string)
+{
+ config::string_node node;
+ node.set("");
+ ATF_REQUIRE_EQ("", node.to_string());
+ node.set("aaa");
+ ATF_REQUIRE_EQ("aaa", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__deep_copy);
+ATF_TEST_CASE_BODY(strings_set_node__deep_copy)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("foo");
+ node.set(value);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::strings_set_node* copy =
+ static_cast< config::strings_set_node* >(raw_copy);
+ value.insert("bar");
+ ATF_REQUIRE_EQ(1, copy->value().size());
+ copy->set(value);
+ ATF_REQUIRE_EQ(1, node.value().size());
+ ATF_REQUIRE_EQ(2, copy->value().size());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__is_set_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__is_set_and_set)
+{
+ std::set< std::string > value;
+ value.insert("foo");
+
+ config::strings_set_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(value);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__value_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__value_and_set)
+{
+ std::set< std::string > value;
+ value.insert("first");
+
+ config::strings_set_node node;
+ node.set(value);
+ ATF_REQUIRE(value == node.value());
+ value.clear();
+ node.set(value);
+ value.insert("second");
+ ATF_REQUIRE(node.value().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__set_string);
+ATF_TEST_CASE_BODY(strings_set_node__set_string)
+{
+ config::strings_set_node node;
+ {
+ std::set< std::string > expected;
+ expected.insert("abcd");
+ expected.insert("efgh");
+
+ node.set_string("abcd efgh");
+ ATF_REQUIRE(expected == node.value());
+ }
+ {
+ std::set< std::string > expected;
+ expected.insert("1234");
+
+ node.set_string(" 1234 ");
+ ATF_REQUIRE(expected == node.value());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__to_string);
+ATF_TEST_CASE_BODY(strings_set_node__to_string)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("second");
+ value.insert("first");
+ node.set(value);
+ ATF_REQUIRE_EQ("first second", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set)
+{
+ validation_node node;
+ node.set(1234);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(12345));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set_string);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set_string)
+{
+ validation_node node;
+ node.set_string("1234");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("12345"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set);
+ATF_TEST_CASE_BODY(base_set_node__validate_set)
+{
+ set_validation_node node;
+ set_validation_node::value_type values;
+ values.insert("foo");
+ values.insert("bar");
+ node.set(values);
+ values.insert("throw");
+ values.insert("baz");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(values));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set_string);
+ATF_TEST_CASE_BODY(base_set_node__validate_set_string)
+{
+ set_validation_node node;
+ node.set_string("foo bar");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("foo bar throw baz"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, bool_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, bool_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, string_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, string_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, string_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set_string);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set_string);
+}
diff --git a/utils/config/parser.cpp b/utils/config/parser.cpp
new file mode 100644
index 000000000000..7bfe5517fdd0
--- /dev/null
+++ b/utils/config/parser.cpp
@@ -0,0 +1,181 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/parser.hpp"
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/lua_module.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace config = utils::config;
+
+
+// History of configuration file versions:
+//
+// 2 - Changed the syntax() call to take only a version number, instead of the
+// word 'config' as the first argument and the version as the second one.
+// Files now start with syntax(2) instead of syntax('config', 1).
+//
+// 1 - Initial version.
+
+
+/// Internal implementation of the parser.
+struct utils::config::parser::impl : utils::noncopyable {
+ /// Pointer to the parent parser. Needed for callbacks.
+ parser* _parent;
+
+ /// The Lua state used by this parser to process the configuration file.
+ lutok::state _state;
+
+ /// The tree to be filed in by the configuration parameters, as provided by
+ /// the caller.
+ config::tree& _tree;
+
+ /// Whether syntax() has been called or not.
+ bool _syntax_called;
+
+ /// Constructs a new implementation.
+ ///
+ /// \param parent_ Pointer to the class being constructed.
+ /// \param config_tree_ The configuration tree provided by the user.
+ impl(parser* const parent_, tree& config_tree_) :
+ _parent(parent_), _tree(config_tree_), _syntax_called(false)
+ {
+ }
+
+ friend void lua_syntax(lutok::state&);
+
+ /// Callback executed by the Lua syntax() function.
+ ///
+ /// \param syntax_version The syntax format version as provided by the
+ /// configuration file in the call to syntax().
+ void
+ syntax_callback(const int syntax_version)
+ {
+ if (_syntax_called)
+ throw syntax_error("syntax() can only be called once");
+ _syntax_called = true;
+
+ // Allow the parser caller to populate the tree with its own schema
+ // depending on the format/version combination.
+ _parent->setup(_tree, syntax_version);
+
+ // Export the config module to the Lua state so that all global variable
+ // accesses are redirected to the configuration tree.
+ config::redirect(_state, _tree);
+ }
+};
+
+
+namespace {
+
+
+static int
+lua_syntax(lutok::state& state)
+{
+ if (!state.is_number(-1))
+ throw config::value_error("Last argument to syntax must be a number");
+ const int syntax_version = state.to_integer(-1);
+
+ if (syntax_version == 1) {
+ if (state.get_top() != 2)
+ throw config::value_error("Version 1 files need two arguments to "
+ "syntax()");
+ if (!state.is_string(-2) || state.to_string(-2) != "config")
+ throw config::value_error("First argument to syntax must be "
+ "'config' for version 1 files");
+ } else {
+ if (state.get_top() != 1)
+ throw config::value_error("syntax() only takes one argument");
+ }
+
+ state.get_global("_config_parser");
+ config::parser::impl* impl =
+ *state.to_userdata< config::parser::impl* >(-1);
+ state.pop(1);
+
+ impl->syntax_callback(syntax_version);
+
+ return 0;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new parser.
+///
+/// \param [in,out] config_tree The configuration tree into which the values set
+/// in the configuration file will be stored.
+config::parser::parser(tree& config_tree) :
+ _pimpl(new impl(this, config_tree))
+{
+ lutok::stack_cleaner cleaner(_pimpl->_state);
+
+ _pimpl->_state.push_cxx_function(lua_syntax);
+ _pimpl->_state.set_global("syntax");
+ *_pimpl->_state.new_userdata< config::parser::impl* >() = _pimpl.get();
+ _pimpl->_state.set_global("_config_parser");
+}
+
+
+/// Destructor.
+config::parser::~parser(void)
+{
+}
+
+
+/// Parses a configuration file.
+///
+/// \post The tree registered during the construction of this class is updated
+/// to contain the values read from the configuration file. If the processing
+/// fails, the state of the output tree is undefined.
+///
+/// \param file The path to the file to process.
+///
+/// \throw syntax_error If there is any problem processing the file.
+void
+config::parser::parse(const fs::path& file)
+{
+ try {
+ lutok::do_file(_pimpl->_state, file.str(), 0, 0, 0);
+ } catch (const lutok::error& e) {
+ throw syntax_error(e.what());
+ }
+
+ if (!_pimpl->_syntax_called)
+ throw syntax_error("No syntax defined (no call to syntax() found)");
+}
diff --git a/utils/config/parser.hpp b/utils/config/parser.hpp
new file mode 100644
index 000000000000..cb69e756cbe8
--- /dev/null
+++ b/utils/config/parser.hpp
@@ -0,0 +1,95 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/parser.hpp
+/// Utilities to read a configuration file into memory.
+
+#if !defined(UTILS_CONFIG_PARSER_HPP)
+#define UTILS_CONFIG_PARSER_HPP
+
+#include "utils/config/parser_fwd.hpp"
+
+#include <memory>
+
+#include "utils/config/tree_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// A configuration parser.
+///
+/// This parser is a class rather than a function because we need to support
+/// callbacks to perform the initialization of the config file schema. The
+/// configuration files always start with a call to syntax(), which define the
+/// particular version of the schema being used. Depending on such version, the
+/// layout of the internal tree representation needs to be different.
+///
+/// A parser implementation must provide a setup() method to set up the
+/// configuration schema based on the particular combination of syntax format
+/// and version specified on the file.
+///
+/// Parser objects are not supposed to be reused, and specific trees are not
+/// supposed to be passed to multiple parsers (even if sequentially). Doing so
+/// will cause all kinds of inconsistencies in the managed tree itself or in the
+/// Lua state.
+class parser : noncopyable {
+public:
+ struct impl;
+
+private:
+ /// Pointer to the internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ /// Hook to initialize the tree keys before reading the file.
+ ///
+ /// This hook gets called when the configuration file defines its specific
+ /// format by calling the syntax() function. We have to delay the tree
+ /// initialization until this point because, before we know what version of
+ /// a configuration file we are parsing, we cannot know what keys are valid.
+ ///
+ /// \param [in,out] config_tree The tree in which to define the key
+ /// structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ virtual void setup(tree& config_tree, const int syntax_version) = 0;
+
+public:
+ explicit parser(tree&);
+ virtual ~parser(void);
+
+ void parse(const fs::path&);
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_HPP)
diff --git a/utils/config/parser_fwd.hpp b/utils/config/parser_fwd.hpp
new file mode 100644
index 000000000000..6278b6c95c12
--- /dev/null
+++ b/utils/config/parser_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/parser_fwd.hpp
+/// Forward declarations for utils/config/parser.hpp
+
+#if !defined(UTILS_CONFIG_PARSER_FWD_HPP)
+#define UTILS_CONFIG_PARSER_FWD_HPP
+
+namespace utils {
+namespace config {
+
+
+class parser;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_FWD_HPP)
diff --git a/utils/config/parser_test.cpp b/utils/config/parser_test.cpp
new file mode 100644
index 000000000000..f5445f55c490
--- /dev/null
+++ b/utils/config/parser_test.cpp
@@ -0,0 +1,252 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/parser.hpp"
+
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/parser.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+
+namespace config = utils::config;
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Implementation of a parser for testing purposes.
+class mock_parser : public config::parser {
+ /// Initializes the tree keys before reading the file.
+ ///
+ /// \param [in,out] tree The tree in which to define the key structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ void
+ setup(config::tree& tree, const int syntax_version)
+ {
+ if (syntax_version == 1) {
+ // Do nothing on config_tree.
+ } else if (syntax_version == 2) {
+ tree.define< config::string_node >("top_string");
+ tree.define< config::int_node >("inner.int");
+ tree.define_dynamic("inner.dynamic");
+ } else {
+ throw std::runtime_error(F("Unknown syntax version %s") %
+ syntax_version);
+ }
+ }
+
+public:
+ /// Initializes a parser.
+ ///
+ /// \param tree The mock config tree to parse.
+ mock_parser(config::tree& tree) :
+ config::parser(tree)
+ {
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__ok);
+ATF_TEST_CASE_BODY(no_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "local foo = 'value'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::string_node >("foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__unknown_key);
+ATF_TEST_CASE_BODY(no_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "foo = 'value'\n");
+
+ config::tree tree;
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "foo",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__ok);
+ATF_TEST_CASE_BODY(some_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "inner.int = 12345\n"
+ "inner.dynamic.foo = 78\n"
+ "inner.dynamic.bar = 'some text'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("inner.int"));
+ ATF_REQUIRE_EQ("78",
+ tree.lookup< config::string_node >("inner.dynamic.foo"));
+ ATF_REQUIRE_EQ("some text",
+ tree.lookup< config::string_node >("inner.dynamic.bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__not_strict);
+ATF_TEST_CASE_BODY(some_keys__not_strict)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "unknown_string = 'bar'\n"
+ "top_string = 'baz'\n");
+
+ config::tree tree(false);
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("baz", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE(!tree.is_set("unknown_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__unknown_key);
+ATF_TEST_CASE_BODY(some_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string2 = 'foo'\n");
+ config::tree tree1;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'top_string2'",
+ mock_parser(tree1).parse(fs::path("output.lua")));
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "inner.int2 = 12345\n");
+ config::tree tree2;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'inner.int2'",
+ mock_parser(tree2).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_syntax);
+ATF_TEST_CASE_BODY(invalid_syntax)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax(56)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown syntax version 56",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_deprecated_format);
+ATF_TEST_CASE_BODY(syntax_deprecated_format)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax('config', 1)\n");
+ (void)mock_parser(tree).parse(fs::path("output.lua"));
+
+ atf::utils::create_file("output.lua", "syntax('foo', 1)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "must be 'config'",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ atf::utils::create_file("output.lua", "syntax('config', 2)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "only takes one argument",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_not_called);
+ATF_TEST_CASE_BODY(syntax_not_called)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file("output.lua", "var = 3\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "No syntax defined",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE(!tree.is_set("var"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_called_more_than_once);
+ATF_TEST_CASE_BODY(syntax_called_more_than_once)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "var = 3\n"
+ "syntax(2)\n"
+ "var = 5\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "syntax\\(\\) can only be called once",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE_EQ(3, tree.lookup< config::int_node >("var"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, no_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, no_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, some_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, some_keys__not_strict);
+ ATF_ADD_TEST_CASE(tcs, some_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_syntax);
+ ATF_ADD_TEST_CASE(tcs, syntax_deprecated_format);
+ ATF_ADD_TEST_CASE(tcs, syntax_not_called);
+ ATF_ADD_TEST_CASE(tcs, syntax_called_more_than_once);
+}
diff --git a/utils/config/tree.cpp b/utils/config/tree.cpp
new file mode 100644
index 000000000000..1aa2d85b89cd
--- /dev/null
+++ b/utils/config/tree.cpp
@@ -0,0 +1,338 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructor.
+///
+/// \param strict Whether keys must be validated at "set" time.
+config::tree::tree(const bool strict) :
+ _strict(strict), _root(new detail::static_inner_node())
+{
+}
+
+
+/// Constructor with a non-empty root.
+///
+/// \param strict Whether keys must be validated at "set" time.
+/// \param root The root to the tree to be owned by this instance.
+config::tree::tree(const bool strict, detail::static_inner_node* root) :
+ _strict(strict), _root(root)
+{
+}
+
+
+/// Destructor.
+config::tree::~tree(void)
+{
+}
+
+
+/// Generates a deep copy of the input tree.
+///
+/// \return A new tree that is an exact copy of this tree.
+config::tree
+config::tree::deep_copy(void) const
+{
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(_root->deep_copy());
+ return config::tree(_strict, new_root);
+}
+
+
+/// Combines two trees.
+///
+/// By combination we understand a new tree that contains the full key space of
+/// the two input trees and, for the keys that match, respects the value of the
+/// right-hand side (aka "other") tree.
+///
+/// Any nodes marked as dynamic "win" over non-dynamic nodes and the resulting
+/// tree will have the dynamic property set on those.
+///
+/// \param overrides The tree to use as value overrides.
+///
+/// \return The combined tree.
+///
+/// \throw bad_combination_error If the two trees cannot be combined; for
+/// example, if a single key represents an inner node in one tree but a leaf
+/// node in the other one.
+config::tree
+config::tree::combine(const tree& overrides) const
+{
+ const detail::static_inner_node* other_root =
+ dynamic_cast< const detail::static_inner_node * >(
+ overrides._root.get());
+
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(
+ _root->combine(detail::tree_key(), other_root));
+ return config::tree(_strict, new_root);
+}
+
+
+/// Registers a node as being dynamic.
+///
+/// This operation creates the given key as an inner node. Further set
+/// operations that trespass this node will automatically create any missing
+/// keys.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+void
+config::tree::define_dynamic(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< detail::dynamic_inner_node >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG("define() failing due to key errors is a programming "
+ "mistake: " + std::string(e.what()));
+ }
+}
+
+
+/// Checks if a given node is set.
+///
+/// \param dotted_key The key to be checked.
+///
+/// \return True if the key is set to a specific value (not just defined).
+/// False if the key is not set or if the key does not exist.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+bool
+config::tree::is_set(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(
+ *raw_node);
+ return child.is_set();
+ } catch (const std::bad_cast& unused_error) {
+ return false;
+ }
+ } catch (const unknown_key_error& unused_error) {
+ return false;
+ }
+}
+
+
+/// Pushes a leaf node's value onto the Lua stack.
+///
+/// \param dotted_key The key to be pushed.
+/// \param state The Lua state into which to push the key's value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::push_lua(const std::string& dotted_key, lutok::state& state) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ child.push_lua(state);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets a leaf node's value from a value in the Lua stack.
+///
+/// \param dotted_key The key to be set.
+/// \param state The Lua state from which to retrieve the value.
+/// \param value_index The position in the Lua stack holding the value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_lua(const std::string& dotted_key, lutok::state& state,
+ const int value_index)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_lua(state, value_index);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Gets the value of a node as a plain string.
+///
+/// \param dotted_key The key to be looked up.
+///
+/// \return The value of the located node as a string.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+std::string
+config::tree::lookup_string(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ return child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key from a string value.
+///
+/// This respects the native types of all the nodes that have been predefined.
+/// For new nodes under a dynamic subtree, this has no mechanism of determining
+/// what type they need to have, so they are created as plain string nodes.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param raw_value The string representation of the value to set the node to.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_string(const std::string& dotted_key,
+ const std::string& raw_value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_string(raw_value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Converts the tree to a collection of key/value string pairs.
+///
+/// \param dotted_key Subtree from which to start the export.
+/// \param strip_key If true, remove the dotted_key prefix from the resulting
+/// properties.
+///
+/// \return A map of keys to values in their textual representation.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+/// \throw value_error If the provided key points to a leaf.
+config::properties_map
+config::tree::all_properties(const std::string& dotted_key,
+ const bool strip_key) const
+{
+ PRE(!strip_key || !dotted_key.empty());
+
+ properties_map properties;
+
+ detail::tree_key key;
+ const detail::base_node* raw_node;
+ if (dotted_key.empty()) {
+ raw_node = _root.get();
+ } else {
+ key = detail::parse_key(dotted_key);
+ raw_node = _root->lookup_ro(key, 0);
+ }
+ try {
+ const detail::inner_node& child =
+ dynamic_cast< const detail::inner_node& >(*raw_node);
+ child.all_properties(properties, key);
+ } catch (const std::bad_cast& unused_error) {
+ INV(!dotted_key.empty());
+ throw value_error(F("Cannot export properties from a leaf node; "
+ "'%s' given") % dotted_key);
+ }
+
+ if (strip_key) {
+ properties_map stripped;
+ for (properties_map::const_iterator iter = properties.begin();
+ iter != properties.end(); ++iter) {
+ stripped[(*iter).first.substr(dotted_key.length() + 1)] =
+ (*iter).second;
+ }
+ properties = stripped;
+ }
+
+ return properties;
+}
+
+
+/// Equality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are equal; false otherwise.
+bool
+config::tree::operator==(const tree& other) const
+{
+ // TODO(jmmv): Would be nicer to perform the comparison directly on the
+ // nodes, instead of exporting the values to strings first.
+ return _root == other._root || all_properties() == other.all_properties();
+}
+
+
+/// Inequality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are different; false otherwise.
+bool
+config::tree::operator!=(const tree& other) const
+{
+ return !(*this == other);
+}
diff --git a/utils/config/tree.hpp b/utils/config/tree.hpp
new file mode 100644
index 000000000000..cad0a9b4fc0b
--- /dev/null
+++ b/utils/config/tree.hpp
@@ -0,0 +1,128 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/tree.hpp
+/// Data type to represent a tree of arbitrary values with string keys.
+
+#if !defined(UTILS_CONFIG_TREE_HPP)
+#define UTILS_CONFIG_TREE_HPP
+
+#include "utils/config/tree_fwd.hpp"
+
+#include <memory>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Representation of a tree.
+///
+/// The string keys of the tree are in dotted notation and actually represent
+/// path traversals through the nodes.
+///
+/// Our trees are "strictly-keyed": keys must be defined as "existent" before
+/// their values can be set. Defining a key is a separate action from setting
+/// its value. The rationale is that we want to be able to control what keys
+/// get defined: because trees are used to hold configuration, we want to catch
+/// typos as early as possible. Also, users cannot set keys unless the types
+/// are known in advance because our leaf nodes are strictly typed.
+///
+/// However, there is an exception to the strict keys: the inner nodes of the
+/// tree can be static or dynamic. Static inner nodes have a known subset of
+/// children and attempting to set keys not previously defined will result in an
+/// error. Dynamic inner nodes do not have a predefined set of keys and can be
+/// used to accept arbitrary user input.
+///
+/// For simplicity reasons, we force the root of the tree to be a static inner
+/// node. In other words, the root can never contain a value by itself and this
+/// is not a problem because the root is not addressable by the key space.
+/// Additionally, the root is strict so all of its direct children must be
+/// explicitly defined.
+///
+/// This is, effectively, a simple wrapper around the node representing the
+/// root. Having a separate class aids in clearly representing the concept of a
+/// tree and all of its public methods. Also, the tree accepts dotted notations
+/// for the keys while the internal structures do not.
+///
+/// Note that trees are shallow-copied unless a deep copy is requested with
+/// deep_copy().
+class tree {
+ /// Whether keys must be validated at "set" time.
+ bool _strict;
+
+ /// The root of the tree.
+ std::shared_ptr< detail::static_inner_node > _root;
+
+ tree(const bool, detail::static_inner_node*);
+
+public:
+ tree(const bool = true);
+ ~tree(void);
+
+ tree deep_copy(void) const;
+ tree combine(const tree&) const;
+
+ template< class LeafType >
+ void define(const std::string&);
+
+ void define_dynamic(const std::string&);
+
+ bool is_set(const std::string&) const;
+
+ template< class LeafType >
+ const typename LeafType::value_type& lookup(const std::string&) const;
+ template< class LeafType >
+ typename LeafType::value_type& lookup_rw(const std::string&);
+
+ template< class LeafType >
+ void set(const std::string&, const typename LeafType::value_type&);
+
+ void push_lua(const std::string&, lutok::state&) const;
+ void set_lua(const std::string&, lutok::state&, const int);
+
+ std::string lookup_string(const std::string&) const;
+ void set_string(const std::string&, const std::string&);
+
+ properties_map all_properties(const std::string& = "",
+ const bool = false) const;
+
+ bool operator==(const tree&) const;
+ bool operator!=(const tree&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_HPP)
diff --git a/utils/config/tree.ipp b/utils/config/tree.ipp
new file mode 100644
index 000000000000..a79acc3be184
--- /dev/null
+++ b/utils/config/tree.ipp
@@ -0,0 +1,156 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.hpp"
+
+#if !defined(UTILS_CONFIG_TREE_IPP)
+#define UTILS_CONFIG_TREE_IPP
+
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \tparam LeafType The node type of the leaf we are defining.
+/// \param dotted_key The key to be registered in dotted representation.
+template< class LeafType >
+void
+config::tree::define(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< LeafType >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG(F("define() failing due to key errors is a programming "
+ "mistake: %s") % e.what());
+ }
+}
+
+
+/// Gets a read-only reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+const typename LeafType::value_type&
+config::tree::lookup(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const LeafType& child = dynamic_cast< const LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Gets a read-write reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+typename LeafType::value_type&
+config::tree::lookup_rw(const std::string& dotted_key)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< LeafType >);
+ try {
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are setting.
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param value The value to set into the node.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+void
+config::tree::set(const std::string& dotted_key,
+ const typename LeafType::value_type& value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ leaf_node* raw_node = _root->lookup_rw(key, 0,
+ detail::new_node< LeafType >);
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ child.set(value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_IPP)
diff --git a/utils/config/tree_fwd.hpp b/utils/config/tree_fwd.hpp
new file mode 100644
index 000000000000..e494d8c0f4ee
--- /dev/null
+++ b/utils/config/tree_fwd.hpp
@@ -0,0 +1,52 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/tree_fwd.hpp
+/// Forward declarations for utils/config/tree.hpp
+
+#if !defined(UTILS_CONFIG_TREE_FWD_HPP)
+#define UTILS_CONFIG_TREE_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+class tree;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_FWD_HPP)
diff --git a/utils/config/tree_test.cpp b/utils/config/tree_test.cpp
new file mode 100644
index 000000000000..b6efd64a84a6
--- /dev/null
+++ b/utils/config/tree_test.cpp
@@ -0,0 +1,1086 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.ipp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Simple wrapper around an integer value without default constructors.
+///
+/// The purpose of this type is to have a simple class without default
+/// constructors to validate that we can use it as a leaf of a tree.
+class int_wrapper {
+ /// The wrapped integer value.
+ int _value;
+
+public:
+ /// Constructs a new wrapped integer.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit int_wrapper(int value_) :
+ _value(value_)
+ {
+ }
+
+ /// \return The integer value stored by the object.
+ int
+ value(void) const
+ {
+ return _value;
+ }
+};
+
+
+/// Custom tree leaf type for an object without defualt constructors.
+class wrapped_int_node : public config::typed_leaf_node< int_wrapper > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< wrapped_int_node > new_node(new wrapped_int_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(
+ config::typed_leaf_node< int_wrapper >::value().value());
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ int_wrapper new_value(state.to_integer(value_index));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \param raw_value The value to set the node to.
+ void
+ set_string(const std::string& raw_value)
+ {
+ int_wrapper new_value(text::to_type< int >(raw_value));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \return A string representation of the value held by the node.
+ std::string
+ to_string(void) const
+ {
+ return F("%s") %
+ config::typed_leaf_node< int_wrapper >::value().value();
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__one_level);
+ATF_TEST_CASE_BODY(define_set_lookup__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("var2", "hello");
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("var2"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__multiple_levels);
+ATF_TEST_CASE_BODY(define_set_lookup__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define< config::bool_node >("foo.3");
+ tree.define_dynamic("sub.tree");
+
+ tree.set< config::int_node >("foo.bar.1", 42);
+ tree.set< config::string_node >("foo.bar.2", "hello");
+ tree.set< config::bool_node >("foo.3", true);
+ tree.set< config::string_node >("sub.tree.1", "bye");
+ tree.set< config::int_node >("sub.tree.2", 4);
+ tree.set< config::int_node >("sub.tree.3.4", 123);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE(tree.lookup< config::bool_node >("foo.3"));
+ ATF_REQUIRE_EQ(4, tree.lookup< config::int_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ(123, tree.lookup< config::int_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__empty);
+ATF_TEST_CASE_BODY(deep_copy__empty)
+{
+ config::tree tree1;
+ config::tree tree2 = tree1.deep_copy();
+
+ tree1.define< config::bool_node >("var1");
+ // This would crash if the copy shared the internal data.
+ tree2.define< config::int_node >("var1");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__some);
+ATF_TEST_CASE_BODY(deep_copy__some)
+{
+ config::tree tree1;
+ tree1.define< config::bool_node >("this.is.a.var");
+ tree1.set< config::bool_node >("this.is.a.var", true);
+ tree1.define< config::int_node >("this.is.another.var");
+ tree1.set< config::int_node >("this.is.another.var", 34);
+ tree1.define< config::int_node >("and.another");
+ tree1.set< config::int_node >("and.another", 123);
+
+ config::tree tree2 = tree1.deep_copy();
+ tree2.set< config::bool_node >("this.is.a.var", false);
+ tree2.set< config::int_node >("this.is.another.var", 43);
+
+ ATF_REQUIRE( tree1.lookup< config::bool_node >("this.is.a.var"));
+ ATF_REQUIRE(!tree2.lookup< config::bool_node >("this.is.a.var"));
+
+ ATF_REQUIRE_EQ(34, tree1.lookup< config::int_node >("this.is.another.var"));
+ ATF_REQUIRE_EQ(43, tree2.lookup< config::int_node >("this.is.another.var"));
+
+ ATF_REQUIRE_EQ(123, tree1.lookup< config::int_node >("and.another"));
+ ATF_REQUIRE_EQ(123, tree2.lookup< config::int_node >("and.another"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__empty);
+ATF_TEST_CASE_BODY(combine__empty)
+{
+ const config::tree t1, t2;
+ const config::tree combined = t1.combine(t2);
+
+ const config::tree expected;
+ ATF_REQUIRE(expected == combined);
+}
+
+
+static void
+init_tree_for_combine_test(config::tree& tree)
+{
+ tree.define< config::int_node >("int-node");
+ tree.define< config::string_node >("string-node");
+ tree.define< config::int_node >("unused.node");
+ tree.define< config::int_node >("deeper.int.node");
+ tree.define_dynamic("deeper.dynamic");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_overrides);
+ATF_TEST_CASE_BODY(combine__same_layout__no_overrides)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t1.set< config::string_node >("string-node", "foo");
+ t1.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t1 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_base);
+ATF_TEST_CASE_BODY(combine__same_layout__no_base)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t2.set< config::int_node >("int-node", 3);
+ t2.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t2.set_string("deeper.dynamic.first", "value1");
+ t2.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t2 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__mix);
+ATF_TEST_CASE_BODY(combine__same_layout__mix)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t2.set< config::int_node >("int-node", 5);
+ t1.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2.1");
+ t2.set_string("deeper.dynamic.second", "value2.2");
+ t2.set_string("deeper.dynamic.third", "value3");
+ const config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ init_tree_for_combine_test(expected);
+ expected.set< config::int_node >("int-node", 5);
+ expected.set< config::string_node >("string-node", "foo");
+ expected.set< config::int_node >("deeper.int.node", 15);
+ expected.set_string("deeper.dynamic.first", "value1");
+ expected.set_string("deeper.dynamic.second", "value2.2");
+ expected.set_string("deeper.dynamic.third", "value3");
+ ATF_REQUIRE(expected == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__different_layout);
+ATF_TEST_CASE_BODY(combine__different_layout)
+{
+ config::tree t1;
+ t1.define< config::int_node >("common.base1");
+ t1.define< config::int_node >("common.base2");
+ t1.define_dynamic("dynamic.base");
+ t1.define< config::int_node >("unset.base");
+
+ config::tree t2;
+ t2.define< config::int_node >("common.base2");
+ t2.define< config::int_node >("common.base3");
+ t2.define_dynamic("dynamic.other");
+ t2.define< config::int_node >("unset.other");
+
+ t1.set< config::int_node >("common.base1", 1);
+ t1.set< config::int_node >("common.base2", 2);
+ t1.set_string("dynamic.base.first", "foo");
+ t1.set_string("dynamic.base.second", "bar");
+
+ t2.set< config::int_node >("common.base2", 4);
+ t2.set< config::int_node >("common.base3", 3);
+ t2.set_string("dynamic.other.first", "FOO");
+ t2.set_string("dynamic.other.second", "BAR");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define< config::int_node >("common.base1");
+ expected.define< config::int_node >("common.base2");
+ expected.define< config::int_node >("common.base3");
+ expected.define_dynamic("dynamic.base");
+ expected.define_dynamic("dynamic.other");
+ expected.define< config::int_node >("unset.base");
+ expected.define< config::int_node >("unset.other");
+
+ expected.set< config::int_node >("common.base1", 1);
+ expected.set< config::int_node >("common.base2", 4);
+ expected.set< config::int_node >("common.base3", 3);
+ expected.set_string("dynamic.base.first", "foo");
+ expected.set_string("dynamic.base.second", "bar");
+ expected.set_string("dynamic.other.first", "FOO");
+ expected.set_string("dynamic.other.second", "BAR");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined tree should have respected existing but unset nodes. Check
+ // that these calls do not crash.
+ combined.set< config::int_node >("unset.base", 5);
+ combined.set< config::int_node >("unset.other", 5);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__dynamic_wins);
+ATF_TEST_CASE_BODY(combine__dynamic_wins)
+{
+ config::tree t1;
+ t1.define< config::int_node >("inner.leaf1");
+ t1.set< config::int_node >("inner.leaf1", 3);
+
+ config::tree t2;
+ t2.define_dynamic("inner");
+ t2.set_string("inner.leaf2", "4");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define_dynamic("inner");
+ expected.set_string("inner.leaf1", "3");
+ expected.set_string("inner.leaf2", "4");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined inner node should have become dynamic so this call should
+ // not fail.
+ combined.set_string("inner.leaf3", "5");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__inner_leaf_mismatch);
+ATF_TEST_CASE_BODY(combine__inner_leaf_mismatch)
+{
+ config::tree t1;
+ t1.define< config::int_node >("top.foo.bar");
+
+ config::tree t2;
+ t2.define< config::int_node >("top.foo");
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is an inner node in the base tree but a "
+ "leaf node in the overrides tree",
+ t1.combine(t2));
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is a leaf node in the base tree but an "
+ "inner node in the overrides tree",
+ t2.combine(t1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__invalid_key);
+ATF_TEST_CASE_BODY(lookup__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.lookup< config::int_node >("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__unknown_key);
+ATF_TEST_CASE_BODY(lookup__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::int_node >("a.d.100", 0);
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("abc"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar.baz"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.c"));
+ (void)tree.lookup< config::int_node >("a.b.c");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b.c.d"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d"));
+ (void)tree.lookup< config::int_node >("a.d.100");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.101"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.100.3"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.e"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__one_level);
+ATF_TEST_CASE_BODY(is_set__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE( tree.is_set("var1"));
+ ATF_REQUIRE(!tree.is_set("var2"));
+ ATF_REQUIRE( tree.is_set("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__multiple_levels);
+ATF_TEST_CASE_BODY(is_set__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.var1");
+ tree.define< config::string_node >("a.b.var2");
+ tree.define< config::bool_node >("e.var3");
+
+ tree.set< config::int_node >("a.b.var1", 42);
+ tree.set< config::bool_node >("e.var3", false);
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE( tree.is_set("a.b.var1"));
+ ATF_REQUIRE(!tree.is_set("a.b.var1.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("e"));
+ ATF_REQUIRE( tree.is_set("e.var3"));
+ ATF_REQUIRE(!tree.is_set("e.var3.trailing"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__invalid_key);
+ATF_TEST_CASE_BODY(is_set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.is_set(".abc"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key);
+ATF_TEST_CASE_BODY(set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.set< config::int_node >("foo.", 54));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key_value);
+ATF_TEST_CASE_BODY(set__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define_dynamic("a.d");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("foo", 3));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("a", -10));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key);
+ATF_TEST_CASE_BODY(set__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("abc", 2));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("foo.bar.baz", 0));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.c", 100));
+ tree.set< config::int_node >("a.b.c", -3);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.b.c.d", 82));
+ tree.set< config::string_node >("a.d.3", "bar");
+ tree.set< config::string_node >("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.d.4.5", 82));
+ tree.set< config::int_node >("a.d.5.6", 82);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ tree.set< config::int_node >("abc", 2);
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ tree.set< config::int_node >("foo.bar.baz", 0);
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set< config::int_node >("a.c", 100);
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(push_lua__ok);
+ATF_TEST_CASE_BODY(push_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+ tree.set< config::int_node >("top.integer", 5);
+ tree.set< wrapped_int_node >("top.custom", int_wrapper(10));
+ tree.set_string("dynamic.first", "foo");
+
+ lutok::state state;
+ tree.push_lua("top.integer", state);
+ tree.push_lua("top.custom", state);
+ tree.push_lua("dynamic.first", state);
+ ATF_REQUIRE(state.is_number(-3));
+ ATF_REQUIRE_EQ(5, state.to_integer(-3));
+ ATF_REQUIRE(state.is_number(-2));
+ ATF_REQUIRE_EQ(10, state.to_integer(-2));
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("foo", state.to_string(-1));
+ state.pop(3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_lua__ok);
+ATF_TEST_CASE_BODY(set_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+
+ {
+ lutok::state state;
+ state.push_integer(5);
+ state.push_integer(10);
+ state.push_string("foo");
+ tree.set_lua("top.integer", state, -3);
+ tree.set_lua("top.custom", state, -2);
+ tree.set_lua("dynamic.first", state, -1);
+ state.pop(3);
+ }
+
+ ATF_REQUIRE_EQ(5, tree.lookup< config::int_node >("top.integer"));
+ ATF_REQUIRE_EQ(10, tree.lookup< wrapped_int_node >("top.custom").value());
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("dynamic.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_rw);
+ATF_TEST_CASE_BODY(lookup_rw)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ tree.lookup_rw< config::int_node >("var1") += 10;
+ ATF_REQUIRE_EQ(52, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__ok);
+ATF_TEST_CASE_BODY(lookup_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("b.var2");
+ tree.define< config::bool_node >("c.d.var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("b.var2", "hello");
+ tree.set< config::bool_node >("c.d.var3", false);
+
+ ATF_REQUIRE_EQ("42", tree.lookup_string("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup_string("b.var2"));
+ ATF_REQUIRE_EQ("false", tree.lookup_string("c.d.var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__invalid_key);
+ATF_TEST_CASE_BODY(lookup_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.lookup_string(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__unknown_key);
+ATF_TEST_CASE_BODY(lookup_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.c");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b.c.d"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__ok);
+ATF_TEST_CASE_BODY(set_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define_dynamic("sub.tree");
+
+ tree.set_string("foo.bar.1", "42");
+ tree.set_string("foo.bar.2", "hello");
+ tree.set_string("sub.tree.2", "15");
+ tree.set_string("sub.tree.3.4", "bye");
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE_EQ("15", tree.lookup< config::string_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ("bye", tree.lookup< config::string_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key);
+ATF_TEST_CASE_BODY(set_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.set_string(".", "foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key_value);
+ATF_TEST_CASE_BODY(set_string__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo", "abc"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", " -3"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", "3 "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key);
+ATF_TEST_CASE_BODY(set_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("abc", "2"));
+
+ tree.set_string("foo.bar", "15");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("foo.bar.baz", "0"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.c", "100"));
+ tree.set_string("a.b.c", "-3");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.b.c.d", "82"));
+ tree.set_string("a.d.3", "bar");
+ tree.set_string("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.d.4.5", "82"));
+ tree.set_string("a.d.5.6", "82");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set_string__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ tree.set_string("abc", "2");
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set_string("foo.bar", "15");
+ tree.set_string("foo.bar.baz", "0");
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set_string("a.c", "100");
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__none);
+ATF_TEST_CASE_BODY(all_properties__none)
+{
+ const config::tree tree;
+ ATF_REQUIRE(tree.all_properties().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__all_set);
+ATF_TEST_CASE_BODY(all_properties__all_set)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("plain");
+ tree.set< config::int_node >("plain", 1234);
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+ tree.set< config::string_node >("static.second", "some text");
+
+ tree.define_dynamic("dynamic");
+ tree.set< config::string_node >("dynamic.first", "hello");
+ tree.set< config::string_node >("dynamic.second", "bye");
+
+ config::properties_map exp_properties;
+ exp_properties["plain"] = "1234";
+ exp_properties["static.first"] = "-3";
+ exp_properties["static.second"] = "some text";
+ exp_properties["dynamic.first"] = "hello";
+ exp_properties["dynamic.second"] = "bye";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__some_unset);
+ATF_TEST_CASE_BODY(all_properties__some_unset)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+
+ tree.define_dynamic("dynamic");
+
+ config::properties_map exp_properties;
+ exp_properties["static.first"] = "-3";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__inner);
+ATF_TEST_CASE_BODY(all_properties__subtree__inner)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b.c"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.d"));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__leaf);
+ATF_TEST_CASE_BODY(all_properties__subtree__leaf)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__strip_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__strip_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ config::properties_map exp_properties;
+ exp_properties["b.c.first"] = "1";
+ exp_properties["b.c.second"] = "2";
+ exp_properties["d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a", true));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__invalid_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.all_properties("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__unknown_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.define< config::int_node >("root.a.b.c.unset");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.all_properties("root.a.b.c.first.foo"));
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.unset"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__empty)
+{
+ config::tree t1;
+ config::tree t2;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__shallow_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__shallow_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__deep_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__deep_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1.deep_copy();
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__some_contents);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__some_contents)
+{
+ config::tree t1, t2;
+
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::int_node >("root.a.b.c.first");
+ t2.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.define< config::string_node >("another.key");
+ t1.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::string_node >("another.key");
+ t2.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_leaf__no_default_ctor);
+ATF_TEST_CASE_BODY(custom_leaf__no_default_ctor)
+{
+ config::tree tree;
+
+ tree.define< wrapped_int_node >("test1");
+ tree.define< wrapped_int_node >("test2");
+ tree.set< wrapped_int_node >("test1", int_wrapper(5));
+ tree.set< wrapped_int_node >("test2", int_wrapper(10));
+ const int_wrapper& test1 = tree.lookup< wrapped_int_node >("test1");
+ ATF_REQUIRE_EQ(5, test1.value());
+ const int_wrapper& test2 = tree.lookup< wrapped_int_node >("test2");
+ ATF_REQUIRE_EQ(10, test2.value());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__one_level);
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__multiple_levels);
+
+ ATF_ADD_TEST_CASE(tcs, deep_copy__empty);
+ ATF_ADD_TEST_CASE(tcs, deep_copy__some);
+
+ ATF_ADD_TEST_CASE(tcs, combine__empty);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_overrides);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_base);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__mix);
+ ATF_ADD_TEST_CASE(tcs, combine__different_layout);
+ ATF_ADD_TEST_CASE(tcs, combine__dynamic_wins);
+ ATF_ADD_TEST_CASE(tcs, combine__inner_leaf_mismatch);
+
+ ATF_ADD_TEST_CASE(tcs, lookup__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, is_set__one_level);
+ ATF_ADD_TEST_CASE(tcs, is_set__multiple_levels);
+ ATF_ADD_TEST_CASE(tcs, is_set__invalid_key);
+
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, push_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, set_lua__ok);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_rw);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_string__ok);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, all_properties__none);
+ ATF_ADD_TEST_CASE(tcs, all_properties__all_set);
+ ATF_ADD_TEST_CASE(tcs, all_properties__some_unset);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__inner);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__leaf);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__strip_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__shallow_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__some_contents);
+
+ ATF_ADD_TEST_CASE(tcs, custom_leaf__no_default_ctor);
+}
diff --git a/utils/datetime.cpp b/utils/datetime.cpp
new file mode 100644
index 000000000000..ae3fdb62fe55
--- /dev/null
+++ b/utils/datetime.cpp
@@ -0,0 +1,613 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/datetime.hpp"
+
+extern "C" {
+#include <sys/time.h>
+
+#include <time.h>
+}
+
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+
+namespace datetime = utils::datetime;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Fake value for the current time.
+static optional< datetime::timestamp > mock_now = none;
+
+
+} // anonymous namespace
+
+
+/// Creates a zero time delta.
+datetime::delta::delta(void) :
+ seconds(0),
+ useconds(0)
+{
+}
+
+
+/// Creates a time delta.
+///
+/// \param seconds_ The seconds in the delta.
+/// \param useconds_ The microseconds in the delta.
+///
+/// \throw std::runtime_error If the input delta is negative.
+datetime::delta::delta(const int64_t seconds_,
+ const unsigned long useconds_) :
+ seconds(seconds_),
+ useconds(useconds_)
+{
+ if (seconds_ < 0) {
+ throw std::runtime_error(F("Negative deltas are not supported by the "
+ "datetime::delta class; got: %s") % (*this));
+ }
+}
+
+
+/// Converts a time expressed in microseconds to a delta.
+///
+/// \param useconds The amount of microseconds representing the delta.
+///
+/// \return A new delta object.
+///
+/// \throw std::runtime_error If the input delta is negative.
+datetime::delta
+datetime::delta::from_microseconds(const int64_t useconds)
+{
+ if (useconds < 0) {
+ throw std::runtime_error(F("Negative deltas are not supported by the "
+ "datetime::delta class; got: %sus") %
+ useconds);
+ }
+
+ return delta(useconds / 1000000, useconds % 1000000);
+}
+
+
+/// Convers the delta to a flat representation expressed in microseconds.
+///
+/// \return The amount of microseconds that corresponds to this delta.
+int64_t
+datetime::delta::to_microseconds(void) const
+{
+ return seconds * 1000000 + useconds;
+}
+
+
+/// Checks if two time deltas are equal.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two time deltas are equals; false otherwise.
+bool
+datetime::delta::operator==(const datetime::delta& other) const
+{
+ return seconds == other.seconds && useconds == other.useconds;
+}
+
+
+/// Checks if two time deltas are different.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two time deltas are different; false otherwise.
+bool
+datetime::delta::operator!=(const datetime::delta& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Checks if this time delta is shorter than another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is shorter than other; false otherwise.
+bool
+datetime::delta::operator<(const datetime::delta& other) const
+{
+ return seconds < other.seconds ||
+ (seconds == other.seconds && useconds < other.useconds);
+}
+
+
+/// Checks if this time delta is shorter than or equal to another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is shorter than or equal to; false
+/// otherwise.
+bool
+datetime::delta::operator<=(const datetime::delta& other) const
+{
+ return (*this) < other || (*this) == other;
+}
+
+
+/// Checks if this time delta is larger than another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is larger than other; false otherwise.
+bool
+datetime::delta::operator>(const datetime::delta& other) const
+{
+ return seconds > other.seconds ||
+ (seconds == other.seconds && useconds > other.useconds);
+}
+
+
+/// Checks if this time delta is larger than or equal to another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is larger than or equal to; false
+/// otherwise.
+bool
+datetime::delta::operator>=(const datetime::delta& other) const
+{
+ return (*this) > other || (*this) == other;
+}
+
+
+/// Adds a time delta to this one.
+///
+/// \param other The time delta to add.
+///
+/// \return The addition of this time delta with the other time delta.
+datetime::delta
+datetime::delta::operator+(const datetime::delta& other) const
+{
+ return delta::from_microseconds(to_microseconds() +
+ other.to_microseconds());
+}
+
+
+/// Adds a time delta to this one and updates this with the result.
+///
+/// \param other The time delta to add.
+///
+/// \return The addition of this time delta with the other time delta.
+datetime::delta&
+datetime::delta::operator+=(const datetime::delta& other)
+{
+ *this = *this + other;
+ return *this;
+}
+
+
+/// Scales this delta by a positive integral factor.
+///
+/// \param factor The scaling factor.
+///
+/// \return The scaled delta.
+datetime::delta
+datetime::delta::operator*(const std::size_t factor) const
+{
+ return delta::from_microseconds(to_microseconds() * factor);
+}
+
+
+/// Scales this delta by and updates this delta with the result.
+///
+/// \param factor The scaling factor.
+///
+/// \return The scaled delta as a reference to the input object.
+datetime::delta&
+datetime::delta::operator*=(const std::size_t factor)
+{
+ *this = *this * factor;
+ return *this;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+std::ostream&
+datetime::operator<<(std::ostream& output, const delta& object)
+{
+ return (output << object.to_microseconds() << "us");
+}
+
+
+namespace utils {
+namespace datetime {
+
+
+/// Internal representation for datetime::timestamp.
+struct timestamp::impl : utils::noncopyable {
+ /// The raw timestamp as provided by libc.
+ ::timeval data;
+
+ /// Constructs an impl object from initialized data.
+ ///
+ /// \param data_ The raw timestamp to use.
+ impl(const ::timeval& data_) : data(data_)
+ {
+ }
+};
+
+
+} // namespace datetime
+} // namespace utils
+
+
+/// Constructs a new timestamp.
+///
+/// \param pimpl_ An existing impl representation.
+datetime::timestamp::timestamp(std::shared_ptr< impl > pimpl_) :
+ _pimpl(pimpl_)
+{
+}
+
+
+/// Constructs a timestamp from the amount of microseconds since the epoch.
+///
+/// \param value Microseconds since the epoch in UTC. Must be positive.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::from_microseconds(const int64_t value)
+{
+ PRE(value >= 0);
+ ::timeval data;
+ data.tv_sec = static_cast< time_t >(value / 1000000);
+ data.tv_usec = static_cast< suseconds_t >(value % 1000000);
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Constructs a timestamp based on user-friendly values.
+///
+/// \param year The year in the [1900,inf) range.
+/// \param month The month in the [1,12] range.
+/// \param day The day in the [1,30] range.
+/// \param hour The hour in the [0,23] range.
+/// \param minute The minute in the [0,59] range.
+/// \param second The second in the [0,60] range. Yes, that is 60, which can be
+/// the case on leap seconds.
+/// \param microsecond The microsecond in the [0,999999] range.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::from_values(const int year, const int month,
+ const int day, const int hour,
+ const int minute, const int second,
+ const int microsecond)
+{
+ PRE(year >= 1900);
+ PRE(month >= 1 && month <= 12);
+ PRE(day >= 1 && day <= 30);
+ PRE(hour >= 0 && hour <= 23);
+ PRE(minute >= 0 && minute <= 59);
+ PRE(second >= 0 && second <= 60);
+ PRE(microsecond >= 0 && microsecond <= 999999);
+
+ // The code below is quite convoluted. The problem is that we can't assume
+ // that some fields (like tm_zone) of ::tm exist, and thus we can't blindly
+ // set them from the code. Instead of detecting their presence in the
+ // configure script, we just query the current time to initialize such
+ // fields and then we override the ones we are interested in. (There might
+ // be some better way to do this, but I don't know it and the documentation
+ // does not shed much light into how to create your own fake date.)
+
+ const time_t current_time = ::time(NULL);
+
+ ::tm timedata;
+ if (::gmtime_r(&current_time, &timedata) == NULL)
+ UNREACHABLE;
+
+ timedata.tm_sec = second;
+ timedata.tm_min = minute;
+ timedata.tm_hour = hour;
+ timedata.tm_mday = day;
+ timedata.tm_mon = month - 1;
+ timedata.tm_year = year - 1900;
+ // Ignored: timedata.tm_wday
+ // Ignored: timedata.tm_yday
+
+ ::timeval data;
+ data.tv_sec = ::mktime(&timedata);
+ data.tv_usec = static_cast< suseconds_t >(microsecond);
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Constructs a new timestamp representing the current time in UTC.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::now(void)
+{
+ if (mock_now)
+ return mock_now.get();
+
+ ::timeval data;
+ {
+ const int ret = ::gettimeofday(&data, NULL);
+ INV(ret != -1);
+ }
+
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Formats a timestamp.
+///
+/// \param format The format string to use as consumed by strftime(3).
+///
+/// \return The formatted time.
+std::string
+datetime::timestamp::strftime(const std::string& format) const
+{
+ ::tm timedata;
+ // This conversion to time_t is necessary because tv_sec is not guaranteed
+ // to be a time_t. For example, it isn't in NetBSD 5.x
+ ::time_t epoch_seconds;
+ epoch_seconds = _pimpl->data.tv_sec;
+ if (::gmtime_r(&epoch_seconds, &timedata) == NULL)
+ UNREACHABLE_MSG("gmtime_r(3) did not accept the value returned by "
+ "gettimeofday(2)");
+
+ char buf[128];
+ if (::strftime(buf, sizeof(buf), format.c_str(), &timedata) == 0)
+ UNREACHABLE_MSG("Arbitrary-long format strings are unimplemented");
+ return buf;
+}
+
+
+/// Formats a timestamp with the ISO 8601 standard and in UTC.
+///
+/// \return A string with the formatted timestamp.
+std::string
+datetime::timestamp::to_iso8601_in_utc(void) const
+{
+ return F("%s.%06sZ") % strftime("%Y-%m-%dT%H:%M:%S") % _pimpl->data.tv_usec;
+}
+
+
+/// Returns the number of microseconds since the epoch in UTC.
+///
+/// \return A number of microseconds.
+int64_t
+datetime::timestamp::to_microseconds(void) const
+{
+ return static_cast< int64_t >(_pimpl->data.tv_sec) * 1000000 +
+ _pimpl->data.tv_usec;
+}
+
+
+/// Returns the number of seconds since the epoch in UTC.
+///
+/// \return A number of seconds.
+int64_t
+datetime::timestamp::to_seconds(void) const
+{
+ return static_cast< int64_t >(_pimpl->data.tv_sec);
+}
+
+
+/// Sets the current time for testing purposes.
+void
+datetime::set_mock_now(const int year, const int month,
+ const int day, const int hour,
+ const int minute, const int second,
+ const int microsecond)
+{
+ mock_now = timestamp::from_values(year, month, day, hour, minute, second,
+ microsecond);
+}
+
+
+/// Sets the current time for testing purposes.
+///
+/// \param mock_now_ The mock timestamp to set the time to.
+void
+datetime::set_mock_now(const timestamp& mock_now_)
+{
+ mock_now = mock_now_;
+}
+
+
+/// Checks if two timestamps are equal.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two timestamps are equals; false otherwise.
+bool
+datetime::timestamp::operator==(const datetime::timestamp& other) const
+{
+ return _pimpl->data.tv_sec == other._pimpl->data.tv_sec &&
+ _pimpl->data.tv_usec == other._pimpl->data.tv_usec;
+}
+
+
+/// Checks if two timestamps are different.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two timestamps are different; false otherwise.
+bool
+datetime::timestamp::operator!=(const datetime::timestamp& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Checks if a timestamp is before another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes before other; false otherwise.
+bool
+datetime::timestamp::operator<(const datetime::timestamp& other) const
+{
+ return to_microseconds() < other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is before or equal to another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes before other or is equal to it;
+/// false otherwise.
+bool
+datetime::timestamp::operator<=(const datetime::timestamp& other) const
+{
+ return to_microseconds() <= other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is after another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes after other; false otherwise;
+bool
+datetime::timestamp::operator>(const datetime::timestamp& other) const
+{
+ return to_microseconds() > other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is after or equal to another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes after other or is equal to it;
+/// false otherwise.
+bool
+datetime::timestamp::operator>=(const datetime::timestamp& other) const
+{
+ return to_microseconds() >= other.to_microseconds();
+}
+
+
+/// Calculates the addition of a delta to a timestamp.
+///
+/// \param other The delta to add.
+///
+/// \return A new timestamp in the future.
+datetime::timestamp
+datetime::timestamp::operator+(const datetime::delta& other) const
+{
+ return datetime::timestamp::from_microseconds(to_microseconds() +
+ other.to_microseconds());
+}
+
+
+/// Calculates the addition of a delta to this timestamp.
+///
+/// \param other The delta to add.
+///
+/// \return A reference to the modified timestamp.
+datetime::timestamp&
+datetime::timestamp::operator+=(const datetime::delta& other)
+{
+ *this = *this + other;
+ return *this;
+}
+
+
+/// Calculates the subtraction of a delta from a timestamp.
+///
+/// \param other The delta to subtract.
+///
+/// \return A new timestamp in the past.
+datetime::timestamp
+datetime::timestamp::operator-(const datetime::delta& other) const
+{
+ return datetime::timestamp::from_microseconds(to_microseconds() -
+ other.to_microseconds());
+}
+
+
+/// Calculates the subtraction of a delta from this timestamp.
+///
+/// \param other The delta to subtract.
+///
+/// \return A reference to the modified timestamp.
+datetime::timestamp&
+datetime::timestamp::operator-=(const datetime::delta& other)
+{
+ *this = *this - other;
+ return *this;
+}
+
+
+/// Calculates the delta between two timestamps.
+///
+/// \param other The subtrahend.
+///
+/// \return The difference between this object and the other object.
+///
+/// \throw std::runtime_error If the subtraction would result in a negative time
+/// delta, which are currently not supported.
+datetime::delta
+datetime::timestamp::operator-(const datetime::timestamp& other) const
+{
+ if ((*this) < other) {
+ throw std::runtime_error(
+ F("Cannot subtract %s from %s as it would result in a negative "
+ "datetime::delta, which are not supported") % other % (*this));
+ }
+ return datetime::delta::from_microseconds(to_microseconds() -
+ other.to_microseconds());
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+std::ostream&
+datetime::operator<<(std::ostream& output, const timestamp& object)
+{
+ return (output << object.to_microseconds() << "us");
+}
diff --git a/utils/datetime.hpp b/utils/datetime.hpp
new file mode 100644
index 000000000000..0c24f332f6d3
--- /dev/null
+++ b/utils/datetime.hpp
@@ -0,0 +1,140 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/datetime.hpp
+/// Provides date and time-related classes and functions.
+
+#if !defined(UTILS_DATETIME_HPP)
+#define UTILS_DATETIME_HPP
+
+#include "utils/datetime_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstddef>
+#include <memory>
+#include <ostream>
+#include <string>
+
+
+namespace utils {
+namespace datetime {
+
+
+/// Represents a time delta to describe deadlines.
+///
+/// Because we use this class to handle deadlines, we currently do not support
+/// negative deltas.
+class delta {
+public:
+ /// The amount of seconds in the time delta.
+ int64_t seconds;
+
+ /// The amount of microseconds in the time delta.
+ unsigned long useconds;
+
+ delta(void);
+ delta(const int64_t, const unsigned long);
+
+ static delta from_microseconds(const int64_t);
+ int64_t to_microseconds(void) const;
+
+ bool operator==(const delta&) const;
+ bool operator!=(const delta&) const;
+ bool operator<(const delta&) const;
+ bool operator<=(const delta&) const;
+ bool operator>(const delta&) const;
+ bool operator>=(const delta&) const;
+
+ delta operator+(const delta&) const;
+ delta& operator+=(const delta&);
+ // operator- and operator-= do not exist because we do not support negative
+ // deltas. See class docstring.
+ delta operator*(const std::size_t) const;
+ delta& operator*=(const std::size_t);
+};
+
+
+std::ostream& operator<<(std::ostream&, const delta&);
+
+
+/// Represents a fixed date/time.
+///
+/// Timestamps are immutable objects and therefore we can simply use a shared
+/// pointer to hide the implementation type of the date. By not using an auto
+/// pointer, we don't have to worry about providing our own copy constructor and
+/// assignment opertor.
+class timestamp {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ timestamp(std::shared_ptr< impl >);
+
+public:
+ static timestamp from_microseconds(const int64_t);
+ static timestamp from_values(const int, const int, const int,
+ const int, const int, const int,
+ const int);
+ static timestamp now(void);
+
+ std::string strftime(const std::string&) const;
+ std::string to_iso8601_in_utc(void) const;
+ int64_t to_microseconds(void) const;
+ int64_t to_seconds(void) const;
+
+ bool operator==(const timestamp&) const;
+ bool operator!=(const timestamp&) const;
+ bool operator<(const timestamp&) const;
+ bool operator<=(const timestamp&) const;
+ bool operator>(const timestamp&) const;
+ bool operator>=(const timestamp&) const;
+
+ timestamp operator+(const delta&) const;
+ timestamp& operator+=(const delta&);
+ timestamp operator-(const delta&) const;
+ timestamp& operator-=(const delta&);
+ delta operator-(const timestamp&) const;
+};
+
+
+std::ostream& operator<<(std::ostream&, const timestamp&);
+
+
+void set_mock_now(const int, const int, const int, const int, const int,
+ const int, const int);
+void set_mock_now(const timestamp&);
+
+
+} // namespace datetime
+} // namespace utils
+
+#endif // !defined(UTILS_DATETIME_HPP)
diff --git a/utils/datetime_fwd.hpp b/utils/datetime_fwd.hpp
new file mode 100644
index 000000000000..1dd886070a34
--- /dev/null
+++ b/utils/datetime_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/datetime_fwd.hpp
+/// Forward declarations for utils/datetime.hpp
+
+#if !defined(UTILS_DATETIME_FWD_HPP)
+#define UTILS_DATETIME_FWD_HPP
+
+namespace utils {
+namespace datetime {
+
+
+class delta;
+class timestamp;
+
+
+} // namespace datetime
+} // namespace utils
+
+#endif // !defined(UTILS_DATETIME_FWD_HPP)
diff --git a/utils/datetime_test.cpp b/utils/datetime_test.cpp
new file mode 100644
index 000000000000..9f8ff50cd0f8
--- /dev/null
+++ b/utils/datetime_test.cpp
@@ -0,0 +1,593 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/datetime.hpp"
+
+extern "C" {
+#include <time.h>
+#include <unistd.h>
+}
+
+#include <sstream>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+namespace datetime = utils::datetime;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__defaults);
+ATF_TEST_CASE_BODY(delta__defaults)
+{
+ const datetime::delta delta;
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__overrides);
+ATF_TEST_CASE_BODY(delta__overrides)
+{
+ const datetime::delta delta(1, 2);
+ ATF_REQUIRE_EQ(1, delta.seconds);
+ ATF_REQUIRE_EQ(2, delta.useconds);
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error, "Negative.*not supported.*-4999997us",
+ datetime::delta(-5, 3));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__from_microseconds);
+ATF_TEST_CASE_BODY(delta__from_microseconds)
+{
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(0);
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 999999);
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(999999, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 1000000);
+ ATF_REQUIRE_EQ(1, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 10576293);
+ ATF_REQUIRE_EQ(10, delta.seconds);
+ ATF_REQUIRE_EQ(576293, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 123456789123456LL);
+ ATF_REQUIRE_EQ(123456789, delta.seconds);
+ ATF_REQUIRE_EQ(123456, delta.useconds);
+ }
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error, "Negative.*not supported.*-12345us",
+ datetime::delta::from_microseconds(-12345));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__to_microseconds);
+ATF_TEST_CASE_BODY(delta__to_microseconds)
+{
+ ATF_REQUIRE_EQ(0, datetime::delta(0, 0).to_microseconds());
+ ATF_REQUIRE_EQ(999999, datetime::delta(0, 999999).to_microseconds());
+ ATF_REQUIRE_EQ(1000000, datetime::delta(1, 0).to_microseconds());
+ ATF_REQUIRE_EQ(10576293, datetime::delta(10, 576293).to_microseconds());
+ ATF_REQUIRE_EQ(11576293, datetime::delta(10, 1576293).to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__equals);
+ATF_TEST_CASE_BODY(delta__equals)
+{
+ ATF_REQUIRE(datetime::delta() == datetime::delta());
+ ATF_REQUIRE(datetime::delta() == datetime::delta(0, 0));
+ ATF_REQUIRE(datetime::delta(1, 2) == datetime::delta(1, 2));
+
+ ATF_REQUIRE(!(datetime::delta() == datetime::delta(0, 1)));
+ ATF_REQUIRE(!(datetime::delta() == datetime::delta(1, 0)));
+ ATF_REQUIRE(!(datetime::delta(1, 2) == datetime::delta(2, 1)));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__differs);
+ATF_TEST_CASE_BODY(delta__differs)
+{
+ ATF_REQUIRE(!(datetime::delta() != datetime::delta()));
+ ATF_REQUIRE(!(datetime::delta() != datetime::delta(0, 0)));
+ ATF_REQUIRE(!(datetime::delta(1, 2) != datetime::delta(1, 2)));
+
+ ATF_REQUIRE(datetime::delta() != datetime::delta(0, 1));
+ ATF_REQUIRE(datetime::delta() != datetime::delta(1, 0));
+ ATF_REQUIRE(datetime::delta(1, 2) != datetime::delta(2, 1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__sorting);
+ATF_TEST_CASE_BODY(delta__sorting)
+{
+ ATF_REQUIRE(!(datetime::delta() < datetime::delta()));
+ ATF_REQUIRE( datetime::delta() <= datetime::delta());
+ ATF_REQUIRE(!(datetime::delta() > datetime::delta()));
+ ATF_REQUIRE( datetime::delta() >= datetime::delta());
+
+ ATF_REQUIRE(!(datetime::delta(9, 8) < datetime::delta(9, 8)));
+ ATF_REQUIRE( datetime::delta(9, 8) <= datetime::delta(9, 8));
+ ATF_REQUIRE(!(datetime::delta(9, 8) > datetime::delta(9, 8)));
+ ATF_REQUIRE( datetime::delta(9, 8) >= datetime::delta(9, 8));
+
+ ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(4, 8));
+ ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(4, 8));
+ ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(4, 8)));
+ ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(4, 8)));
+
+ ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(2, 8));
+ ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(2, 8));
+ ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(2, 8)));
+ ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(2, 8)));
+
+ ATF_REQUIRE(!(datetime::delta(4, 8) < datetime::delta(2, 5)));
+ ATF_REQUIRE(!(datetime::delta(4, 8) <= datetime::delta(2, 5)));
+ ATF_REQUIRE( datetime::delta(4, 8) > datetime::delta(2, 5));
+ ATF_REQUIRE( datetime::delta(4, 8) >= datetime::delta(2, 5));
+
+ ATF_REQUIRE(!(datetime::delta(2, 8) < datetime::delta(2, 5)));
+ ATF_REQUIRE(!(datetime::delta(2, 8) <= datetime::delta(2, 5)));
+ ATF_REQUIRE( datetime::delta(2, 8) > datetime::delta(2, 5));
+ ATF_REQUIRE( datetime::delta(2, 8) >= datetime::delta(2, 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__addition);
+ATF_TEST_CASE_BODY(delta__addition)
+{
+ using datetime::delta;
+
+ ATF_REQUIRE_EQ(delta(), delta() + delta());
+ ATF_REQUIRE_EQ(delta(0, 10), delta() + delta(0, 10));
+ ATF_REQUIRE_EQ(delta(10, 0), delta(10, 0) + delta());
+
+ ATF_REQUIRE_EQ(delta(1, 234567), delta(0, 1234567) + delta());
+ ATF_REQUIRE_EQ(delta(12, 34), delta(10, 20) + delta(2, 14));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__addition_and_set);
+ATF_TEST_CASE_BODY(delta__addition_and_set)
+{
+ using datetime::delta;
+
+ {
+ delta d;
+ d += delta(3, 5);
+ ATF_REQUIRE_EQ(delta(3, 5), d);
+ }
+ {
+ delta d(1, 2);
+ d += delta(3, 5);
+ ATF_REQUIRE_EQ(delta(4, 7), d);
+ }
+ {
+ delta d(1, 2);
+ ATF_REQUIRE_EQ(delta(4, 7), (d += delta(3, 5)));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__scale);
+ATF_TEST_CASE_BODY(delta__scale)
+{
+ using datetime::delta;
+
+ ATF_REQUIRE_EQ(delta(), delta() * 0);
+ ATF_REQUIRE_EQ(delta(), delta() * 5);
+
+ ATF_REQUIRE_EQ(delta(0, 30), delta(0, 10) * 3);
+ ATF_REQUIRE_EQ(delta(17, 500000), delta(3, 500000) * 5);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__scale_and_set);
+ATF_TEST_CASE_BODY(delta__scale_and_set)
+{
+ using datetime::delta;
+
+ {
+ delta d(3, 5);
+ d *= 2;
+ ATF_REQUIRE_EQ(delta(6, 10), d);
+ }
+ {
+ delta d(8, 0);
+ d *= 8;
+ ATF_REQUIRE_EQ(delta(64, 0), d);
+ }
+ {
+ delta d(3, 5);
+ ATF_REQUIRE_EQ(delta(9, 15), (d *= 3));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__output);
+ATF_TEST_CASE_BODY(delta__output)
+{
+ {
+ std::ostringstream str;
+ str << datetime::delta(15, 8791);
+ ATF_REQUIRE_EQ("15008791us", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << datetime::delta(12345678, 0);
+ ATF_REQUIRE_EQ("12345678000000us", str.str());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__copy);
+ATF_TEST_CASE_BODY(timestamp__copy)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2011, 2, 16, 19, 15, 30, 0);
+ {
+ const datetime::timestamp ts2 = ts1;
+ const datetime::timestamp ts3 = datetime::timestamp::from_values(
+ 2012, 2, 16, 19, 15, 30, 0);
+ ATF_REQUIRE_EQ("2011", ts1.strftime("%Y"));
+ ATF_REQUIRE_EQ("2011", ts2.strftime("%Y"));
+ ATF_REQUIRE_EQ("2012", ts3.strftime("%Y"));
+ }
+ ATF_REQUIRE_EQ("2011", ts1.strftime("%Y"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__from_microseconds);
+ATF_TEST_CASE_BODY(timestamp__from_microseconds)
+{
+ const datetime::timestamp ts = datetime::timestamp::from_microseconds(
+ 1328829351987654LL);
+ ATF_REQUIRE_EQ("2012-02-09 23:15:51", ts.strftime("%Y-%m-%d %H:%M:%S"));
+ ATF_REQUIRE_EQ(1328829351987654LL, ts.to_microseconds());
+ ATF_REQUIRE_EQ(1328829351, ts.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__mock);
+ATF_TEST_CASE_BODY(timestamp__now__mock)
+{
+ datetime::set_mock_now(2011, 2, 21, 18, 5, 10, 0);
+ ATF_REQUIRE_EQ("2011-02-21 18:05:10",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+
+ datetime::set_mock_now(datetime::timestamp::from_values(
+ 2012, 3, 22, 19, 6, 11, 54321));
+ ATF_REQUIRE_EQ("2012-03-22 19:06:11",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+ ATF_REQUIRE_EQ("2012-03-22 19:06:11",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__real);
+ATF_TEST_CASE_BODY(timestamp__now__real)
+{
+ // This test is might fail if we happen to run at the crossing of one
+ // day to the other and the two measures we pick of the current time
+ // differ. This is so unlikely that I haven't bothered to do this in any
+ // other way.
+
+ const time_t just_before = ::time(NULL);
+ const datetime::timestamp now = datetime::timestamp::now();
+
+ ::tm data;
+ char buf[1024];
+ ATF_REQUIRE(::gmtime_r(&just_before, &data) != 0);
+ ATF_REQUIRE(::strftime(buf, sizeof(buf), "%Y-%m-%d", &data) != 0);
+ ATF_REQUIRE_EQ(buf, now.strftime("%Y-%m-%d"));
+
+ ATF_REQUIRE(now.strftime("%Z") == "GMT" || now.strftime("%Z") == "UTC");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__granularity);
+ATF_TEST_CASE_BODY(timestamp__now__granularity)
+{
+ const datetime::timestamp first = datetime::timestamp::now();
+ ::usleep(1);
+ const datetime::timestamp second = datetime::timestamp::now();
+ ATF_REQUIRE(first.to_microseconds() != second.to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__strftime);
+ATF_TEST_CASE_BODY(timestamp__strftime)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 0);
+ ATF_REQUIRE_EQ("2010-12-10", ts1.strftime("%Y-%m-%d"));
+ ATF_REQUIRE_EQ("08:45:50", ts1.strftime("%H:%M:%S"));
+
+ const datetime::timestamp ts2 = datetime::timestamp::from_values(
+ 2011, 2, 16, 19, 15, 30, 0);
+ ATF_REQUIRE_EQ("2011-02-16T19:15:30", ts2.strftime("%Y-%m-%dT%H:%M:%S"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_iso8601_in_utc);
+ATF_TEST_CASE_BODY(timestamp__to_iso8601_in_utc)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 0);
+ ATF_REQUIRE_EQ("2010-12-10T08:45:50.000000Z", ts1.to_iso8601_in_utc());
+
+ const datetime::timestamp ts2= datetime::timestamp::from_values(
+ 2016, 7, 11, 17, 51, 28, 123456);
+ ATF_REQUIRE_EQ("2016-07-11T17:51:28.123456Z", ts2.to_iso8601_in_utc());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_microseconds);
+ATF_TEST_CASE_BODY(timestamp__to_microseconds)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 123456);
+ ATF_REQUIRE_EQ(1291970750123456LL, ts1.to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_seconds);
+ATF_TEST_CASE_BODY(timestamp__to_seconds)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 123456);
+ ATF_REQUIRE_EQ(1291970750, ts1.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__leap_second);
+ATF_TEST_CASE_BODY(timestamp__leap_second)
+{
+ // This is actually a test for from_values(), which is the function that
+ // includes assertions to validate the input parameters.
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2012, 6, 30, 23, 59, 60, 543);
+ ATF_REQUIRE_EQ(1341100800, ts1.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__equals);
+ATF_TEST_CASE_BODY(timestamp__equals)
+{
+ ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) ==
+ datetime::timestamp::from_microseconds(1291970750123456LL));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__differs);
+ATF_TEST_CASE_BODY(timestamp__differs)
+{
+ ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) !=
+ datetime::timestamp::from_microseconds(1291970750123455LL));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__sorting);
+ATF_TEST_CASE_BODY(timestamp__sorting)
+{
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+
+ ATF_REQUIRE(!(ts1 < ts2));
+ ATF_REQUIRE( ts1 <= ts2);
+ ATF_REQUIRE(!(ts1 > ts2));
+ ATF_REQUIRE( ts1 >= ts2);
+ }
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970759123455LL);
+
+ ATF_REQUIRE( ts1 < ts2);
+ ATF_REQUIRE( ts1 <= ts2);
+ ATF_REQUIRE(!(ts1 > ts2));
+ ATF_REQUIRE(!(ts1 >= ts2));
+ }
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970759123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+
+ ATF_REQUIRE(!(ts1 < ts2));
+ ATF_REQUIRE(!(ts1 <= ts2));
+ ATF_REQUIRE( ts1 > ts2);
+ ATF_REQUIRE( ts1 >= ts2);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta);
+ATF_TEST_CASE_BODY(timestamp__add_delta)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234),
+ timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) +
+ delta(30, 1234));
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100),
+ timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) +
+ delta(3602, 5000100));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta_and_set);
+ATF_TEST_CASE_BODY(timestamp__add_delta_and_set)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0);
+ ts += delta(30, 1234);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234),
+ ts);
+ }
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100),
+ ts += delta(3602, 5000100));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta);
+ATF_TEST_CASE_BODY(timestamp__subtract_delta)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321),
+ timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555) -
+ delta(30, 1234));
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300),
+ timestamp::from_values(2014, 12, 11, 21, 43, 8, 400) -
+ delta(3602, 5000100));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta_and_set);
+ATF_TEST_CASE_BODY(timestamp__subtract_delta_and_set)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555);
+ ts -= delta(30, 1234);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321),
+ ts);
+ }
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 8, 400);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300),
+ ts -= delta(3602, 5000100));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtraction);
+ATF_TEST_CASE_BODY(timestamp__subtraction)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123456LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123468LL);
+ const datetime::timestamp ts3 = datetime::timestamp::from_microseconds(
+ 1291970850123456LL);
+
+ ATF_REQUIRE_EQ(datetime::delta(0, 0), ts1 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(0, 12), ts2 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(100, 0), ts3 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(99, 999988), ts3 - ts2);
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error,
+ "Cannot subtract 1291970850123456us from 1291970750123468us "
+ ".*negative datetime::delta.*not supported",
+ ts2 - ts3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__output);
+ATF_TEST_CASE_BODY(timestamp__output)
+{
+ {
+ std::ostringstream str;
+ str << datetime::timestamp::from_microseconds(1291970750123456LL);
+ ATF_REQUIRE_EQ("1291970750123456us", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << datetime::timestamp::from_microseconds(1028309798759812LL);
+ ATF_REQUIRE_EQ("1028309798759812us", str.str());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, delta__defaults);
+ ATF_ADD_TEST_CASE(tcs, delta__overrides);
+ ATF_ADD_TEST_CASE(tcs, delta__from_microseconds);
+ ATF_ADD_TEST_CASE(tcs, delta__to_microseconds);
+ ATF_ADD_TEST_CASE(tcs, delta__equals);
+ ATF_ADD_TEST_CASE(tcs, delta__differs);
+ ATF_ADD_TEST_CASE(tcs, delta__sorting);
+ ATF_ADD_TEST_CASE(tcs, delta__addition);
+ ATF_ADD_TEST_CASE(tcs, delta__addition_and_set);
+ ATF_ADD_TEST_CASE(tcs, delta__scale);
+ ATF_ADD_TEST_CASE(tcs, delta__scale_and_set);
+ ATF_ADD_TEST_CASE(tcs, delta__output);
+
+ ATF_ADD_TEST_CASE(tcs, timestamp__copy);
+ ATF_ADD_TEST_CASE(tcs, timestamp__from_microseconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__mock);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__real);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__granularity);
+ ATF_ADD_TEST_CASE(tcs, timestamp__strftime);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_iso8601_in_utc);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_microseconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_seconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__leap_second);
+ ATF_ADD_TEST_CASE(tcs, timestamp__equals);
+ ATF_ADD_TEST_CASE(tcs, timestamp__differs);
+ ATF_ADD_TEST_CASE(tcs, timestamp__sorting);
+ ATF_ADD_TEST_CASE(tcs, timestamp__add_delta);
+ ATF_ADD_TEST_CASE(tcs, timestamp__add_delta_and_set);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta_and_set);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtraction);
+ ATF_ADD_TEST_CASE(tcs, timestamp__output);
+}
diff --git a/utils/defs.hpp.in b/utils/defs.hpp.in
new file mode 100644
index 000000000000..62fc50d0e525
--- /dev/null
+++ b/utils/defs.hpp.in
@@ -0,0 +1,57 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/defs.hpp
+///
+/// Definitions for compiler and system features autodetected at configuration
+/// time.
+
+#if !defined(UTILS_DEFS_HPP)
+#define UTILS_DEFS_HPP
+
+
+/// Attribute to mark a function as non-returning.
+#define UTILS_NORETURN @ATTRIBUTE_NORETURN@
+
+
+/// Attribute to mark a function as pure.
+#define UTILS_PURE @ATTRIBUTE_PURE@
+
+
+/// Attribute to mark an entity as unused.
+#define UTILS_UNUSED @ATTRIBUTE_UNUSED@
+
+
+/// Unconstifies a pointer.
+///
+/// \param type The target type of the conversion.
+/// \param ptr The pointer to be unconstified.
+#define UTILS_UNCONST(type, ptr) ((type*)(unsigned long)(const void*)(ptr))
+
+
+#endif // !defined(UTILS_DEFS_HPP)
diff --git a/utils/env.cpp b/utils/env.cpp
new file mode 100644
index 000000000000..b0d995c0ff31
--- /dev/null
+++ b/utils/env.cpp
@@ -0,0 +1,200 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/env.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+
+namespace fs = utils::fs;
+
+using utils::none;
+using utils::optional;
+
+
+extern "C" {
+ extern char** environ;
+}
+
+
+/// Gets all environment variables.
+///
+/// \return A mapping of (name, value) pairs describing the environment
+/// variables.
+std::map< std::string, std::string >
+utils::getallenv(void)
+{
+ std::map< std::string, std::string > allenv;
+ for (char** envp = environ; *envp != NULL; envp++) {
+ const std::string oneenv = *envp;
+ const std::string::size_type pos = oneenv.find('=');
+ const std::string name = oneenv.substr(0, pos);
+ const std::string value = oneenv.substr(pos + 1);
+
+ PRE(allenv.find(name) == allenv.end());
+ allenv[name] = value;
+ }
+ return allenv;
+}
+
+
+/// Gets the value of an environment variable.
+///
+/// \param name The name of the environment variable to query.
+///
+/// \return The value of the environment variable if it is defined, or none
+/// otherwise.
+optional< std::string >
+utils::getenv(const std::string& name)
+{
+ const char* value = std::getenv(name.c_str());
+ if (value == NULL) {
+ LD(F("Environment variable '%s' is not defined") % name);
+ return none;
+ } else {
+ LD(F("Environment variable '%s' is '%s'") % name % value);
+ return utils::make_optional(std::string(value));
+ }
+}
+
+
+/// Gets the value of an environment variable with a default fallback.
+///
+/// \param name The name of the environment variable to query.
+/// \param default_value The value to return if the variable is not defined.
+///
+/// \return The value of the environment variable.
+std::string
+utils::getenv_with_default(const std::string& name,
+ const std::string& default_value)
+{
+ const char* value = std::getenv(name.c_str());
+ if (value == NULL) {
+ LD(F("Environment variable '%s' is not defined; using default '%s'") %
+ name % default_value);
+ return default_value;
+ } else {
+ LD(F("Environment variable '%s' is '%s'") % name % value);
+ return value;
+ }
+}
+
+
+/// Gets the value of the HOME environment variable with path validation.
+///
+/// \return The value of the HOME environment variable if it is a valid path;
+/// none if it is not defined or if it contains an invalid path.
+optional< fs::path >
+utils::get_home(void)
+{
+ const optional< std::string > home = utils::getenv("HOME");
+ if (home) {
+ try {
+ return utils::make_optional(fs::path(home.get()));
+ } catch (const fs::error& e) {
+ LW(F("Invalid value '%s' in HOME environment variable: %s") %
+ home.get() % e.what());
+ return none;
+ }
+ } else {
+ return none;
+ }
+}
+
+
+/// Sets the value of an environment variable.
+///
+/// \param name The name of the environment variable to set.
+/// \param val The value to set the environment variable to. May be empty.
+///
+/// \throw std::runtime_error If there is an error setting the environment
+/// variable.
+void
+utils::setenv(const std::string& name, const std::string& val)
+{
+ LD(F("Setting environment variable '%s' to '%s'") % name % val);
+#if defined(HAVE_SETENV)
+ if (::setenv(name.c_str(), val.c_str(), 1) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to set environment variable '%s' to "
+ "'%s': %s") %
+ name % val % std::strerror(original_errno));
+ }
+#elif defined(HAVE_PUTENV)
+ if (::putenv((F("%s=%s") % name % val).c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to set environment variable '%s' to "
+ "'%s': %s") %
+ name % val % std::strerror(original_errno));
+ }
+#else
+# error "Don't know how to set an environment variable."
+#endif
+}
+
+
+/// Unsets an environment variable.
+///
+/// \param name The name of the environment variable to unset.
+///
+/// \throw std::runtime_error If there is an error unsetting the environment
+/// variable.
+void
+utils::unsetenv(const std::string& name)
+{
+ LD(F("Unsetting environment variable '%s'") % name);
+#if defined(HAVE_UNSETENV)
+ if (::unsetenv(name.c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to unset environment variable "
+ "'%s'") %
+ name % std::strerror(original_errno));
+ }
+#elif defined(HAVE_PUTENV)
+ if (::putenv((F("%s=") % name).c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to unset environment variable "
+ "'%s'") %
+ name % std::strerror(original_errno));
+ }
+#else
+# error "Don't know how to unset an environment variable."
+#endif
+}
diff --git a/utils/env.hpp b/utils/env.hpp
new file mode 100644
index 000000000000..2370ee490dc1
--- /dev/null
+++ b/utils/env.hpp
@@ -0,0 +1,58 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/env.hpp
+/// Querying and manipulation of environment variables.
+///
+/// These utility functions wrap the system functions to manipulate the
+/// environment in a portable way and expose their arguments and return values
+/// in a C++-friendly manner.
+
+#if !defined(UTILS_ENV_HPP)
+#define UTILS_ENV_HPP
+
+#include <map>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+
+namespace utils {
+
+
+std::map< std::string, std::string > getallenv(void);
+optional< std::string > getenv(const std::string&);
+std::string getenv_with_default(const std::string&, const std::string&);
+optional< utils::fs::path > get_home(void);
+void setenv(const std::string&, const std::string&);
+void unsetenv(const std::string&);
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_ENV_HPP)
diff --git a/utils/env_test.cpp b/utils/env_test.cpp
new file mode 100644
index 000000000000..1b16266443af
--- /dev/null
+++ b/utils/env_test.cpp
@@ -0,0 +1,167 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/env.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+
+namespace fs = utils::fs;
+
+using utils::optional;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getallenv);
+ATF_TEST_CASE_BODY(getallenv)
+{
+ utils::unsetenv("test-missing");
+ utils::setenv("test-empty", "");
+ utils::setenv("test-text", "some-value");
+
+ const std::map< std::string, std::string > allenv = utils::getallenv();
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-missing");
+ ATF_REQUIRE(iter == allenv.end());
+ }
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-empty");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE((*iter).second.empty());
+ }
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-text");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE_EQ("some-value", (*iter).second);
+ }
+
+ if (utils::getenv("PATH")) {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("PATH");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE_EQ(utils::getenv("PATH").get(), (*iter).second);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getenv);
+ATF_TEST_CASE_BODY(getenv)
+{
+ const optional< std::string > path = utils::getenv("PATH");
+ ATF_REQUIRE(path);
+ ATF_REQUIRE(!path.get().empty());
+
+ ATF_REQUIRE(!utils::getenv("__UNDEFINED_VARIABLE__"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getenv_with_default);
+ATF_TEST_CASE_BODY(getenv_with_default)
+{
+ ATF_REQUIRE("don't use" !=
+ utils::getenv_with_default("PATH", "don't use"));
+
+ ATF_REQUIRE_EQ("foo",
+ utils::getenv_with_default("__UNDEFINED_VARIABLE__", "foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__ok);
+ATF_TEST_CASE_BODY(get_home__ok)
+{
+ const fs::path home("/foo/bar");
+ utils::setenv("HOME", home.str());
+ const optional< fs::path > computed = utils::get_home();
+ ATF_REQUIRE(computed);
+ ATF_REQUIRE_EQ(home, computed.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__missing);
+ATF_TEST_CASE_BODY(get_home__missing)
+{
+ utils::unsetenv("HOME");
+ ATF_REQUIRE(!utils::get_home());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__invalid);
+ATF_TEST_CASE_BODY(get_home__invalid)
+{
+ utils::setenv("HOME", "");
+ ATF_REQUIRE(!utils::get_home());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(setenv);
+ATF_TEST_CASE_BODY(setenv)
+{
+ ATF_REQUIRE(utils::getenv("PATH"));
+ const std::string oldval = utils::getenv("PATH").get();
+ utils::setenv("PATH", "foo-bar");
+ ATF_REQUIRE(utils::getenv("PATH").get() != oldval);
+ ATF_REQUIRE_EQ("foo-bar", utils::getenv("PATH").get());
+
+ ATF_REQUIRE(!utils::getenv("__UNDEFINED_VARIABLE__"));
+ utils::setenv("__UNDEFINED_VARIABLE__", "foo2-bar2");
+ ATF_REQUIRE_EQ("foo2-bar2", utils::getenv("__UNDEFINED_VARIABLE__").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unsetenv);
+ATF_TEST_CASE_BODY(unsetenv)
+{
+ ATF_REQUIRE(utils::getenv("PATH"));
+ utils::unsetenv("PATH");
+ ATF_REQUIRE(!utils::getenv("PATH"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, getallenv);
+
+ ATF_ADD_TEST_CASE(tcs, getenv);
+
+ ATF_ADD_TEST_CASE(tcs, getenv_with_default);
+
+ ATF_ADD_TEST_CASE(tcs, get_home__ok);
+ ATF_ADD_TEST_CASE(tcs, get_home__missing);
+ ATF_ADD_TEST_CASE(tcs, get_home__invalid);
+
+ ATF_ADD_TEST_CASE(tcs, setenv);
+
+ ATF_ADD_TEST_CASE(tcs, unsetenv);
+}
diff --git a/utils/format/Kyuafile b/utils/format/Kyuafile
new file mode 100644
index 000000000000..344ae455422c
--- /dev/null
+++ b/utils/format/Kyuafile
@@ -0,0 +1,7 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="containers_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="formatter_test"}
diff --git a/utils/format/Makefile.am.inc b/utils/format/Makefile.am.inc
new file mode 100644
index 000000000000..a37fc4057079
--- /dev/null
+++ b/utils/format/Makefile.am.inc
@@ -0,0 +1,59 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/format/containers.hpp
+libutils_a_SOURCES += utils/format/containers.ipp
+libutils_a_SOURCES += utils/format/exceptions.cpp
+libutils_a_SOURCES += utils/format/exceptions.hpp
+libutils_a_SOURCES += utils/format/formatter.cpp
+libutils_a_SOURCES += utils/format/formatter.hpp
+libutils_a_SOURCES += utils/format/formatter_fwd.hpp
+libutils_a_SOURCES += utils/format/formatter.ipp
+libutils_a_SOURCES += utils/format/macros.hpp
+
+if WITH_ATF
+tests_utils_formatdir = $(pkgtestsdir)/utils/format
+
+tests_utils_format_DATA = utils/format/Kyuafile
+EXTRA_DIST += $(tests_utils_format_DATA)
+
+tests_utils_format_PROGRAMS = utils/format/containers_test
+utils_format_containers_test_SOURCES = utils/format/containers_test.cpp
+utils_format_containers_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_containers_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_format_PROGRAMS += utils/format/exceptions_test
+utils_format_exceptions_test_SOURCES = utils/format/exceptions_test.cpp
+utils_format_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_format_PROGRAMS += utils/format/formatter_test
+utils_format_formatter_test_SOURCES = utils/format/formatter_test.cpp
+utils_format_formatter_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_formatter_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/format/containers.hpp b/utils/format/containers.hpp
new file mode 100644
index 000000000000..7334c250de4e
--- /dev/null
+++ b/utils/format/containers.hpp
@@ -0,0 +1,66 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/format/containers.hpp
+/// Overloads to support formatting various base container types.
+
+#if !defined(UTILS_FORMAT_CONTAINERS_HPP)
+#define UTILS_FORMAT_CONTAINERS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <utility>
+#include <vector>
+
+
+// This is ugly but necessary for C++ name resolution. Unsure if we'd do it
+// differently...
+namespace std {
+
+
+template< typename K, typename V >
+std::ostream& operator<<(std::ostream&, const std::map< K, V >&);
+
+template< typename T1, typename T2 >
+std::ostream& operator<<(std::ostream&, const std::pair< T1, T2 >&);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::shared_ptr< T >);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::set< T >&);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::vector< T >&);
+
+
+} // namespace std
+
+#endif // !defined(UTILS_FORMAT_CONTAINERS_HPP)
diff --git a/utils/format/containers.ipp b/utils/format/containers.ipp
new file mode 100644
index 000000000000..11d8e2914149
--- /dev/null
+++ b/utils/format/containers.ipp
@@ -0,0 +1,138 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_FORMAT_CONTAINERS_IPP)
+#define UTILS_FORMAT_CONTAINERS_IPP
+
+#include "utils/format/containers.hpp"
+
+#include <ostream>
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename K, typename V >
+std::ostream&
+std::operator<<(std::ostream& output, const std::map< K, V >& object)
+{
+ output << "map(";
+ typename std::map< K, V >::size_type counter = 0;
+ for (typename std::map< K, V >::const_iterator iter = object.begin();
+ iter != object.end(); ++iter, ++counter) {
+ if (counter != 0)
+ output << ", ";
+ output << (*iter).first << "=" << (*iter).second;
+ }
+ output << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T1, typename T2 >
+std::ostream&
+std::operator<<(std::ostream& output, const std::pair< T1, T2 >& object)
+{
+ output << "pair(" << object.first << ", " << object.second << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::shared_ptr< T > object)
+{
+ if (object.get() == NULL) {
+ output << "<NULL>";
+ } else {
+ output << *object;
+ }
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::set< T >& object)
+{
+ output << "set(";
+ typename std::set< T >::size_type counter = 0;
+ for (typename std::set< T >::const_iterator iter = object.begin();
+ iter != object.end(); ++iter, ++counter) {
+ if (counter != 0)
+ output << ", ";
+ output << (*iter);
+ }
+ output << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::vector< T >& object)
+{
+ output << "[";
+ for (typename std::vector< T >::size_type i = 0; i < object.size(); ++i) {
+ if (i != 0)
+ output << ", ";
+ output << object[i];
+ }
+ output << "]";
+ return output;
+}
+
+
+#endif // !defined(UTILS_FORMAT_CONTAINERS_IPP)
diff --git a/utils/format/containers_test.cpp b/utils/format/containers_test.cpp
new file mode 100644
index 000000000000..e1c452da2df6
--- /dev/null
+++ b/utils/format/containers_test.cpp
@@ -0,0 +1,190 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/format/containers.ipp"
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+
+
+namespace {
+
+
+/// Formats a value and compares it to an expected string.
+///
+/// \tparam T The type of the value to format.
+/// \param expected Expected formatted text.
+/// \param actual The value to format.
+///
+/// \post Fails the test case if the formatted actual value does not match
+/// the provided expected string.
+template< typename T >
+static void
+do_check(const char* expected, const T& actual)
+{
+ std::ostringstream str;
+ str << actual;
+ ATF_REQUIRE_EQ(expected, str.str());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_map__empty);
+ATF_TEST_CASE_BODY(std_map__empty)
+{
+ do_check("map()", std::map< char, char >());
+ do_check("map()", std::map< int, long >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_map__some);
+ATF_TEST_CASE_BODY(std_map__some)
+{
+ {
+ std::map< char, int > v;
+ v['b'] = 123;
+ v['z'] = 321;
+ do_check("map(b=123, z=321)", v);
+ }
+
+ {
+ std::map< int, std::string > v;
+ v[5] = "first";
+ v[2] = "second";
+ v[8] = "third";
+ do_check("map(2=second, 5=first, 8=third)", v);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_pair);
+ATF_TEST_CASE_BODY(std_pair)
+{
+ do_check("pair(5, b)", std::pair< int, char >(5, 'b'));
+ do_check("pair(foo bar, baz)",
+ std::pair< std::string, std::string >("foo bar", "baz"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_shared_ptr__null);
+ATF_TEST_CASE_BODY(std_shared_ptr__null)
+{
+ do_check("<NULL>", std::shared_ptr< char >());
+ do_check("<NULL>", std::shared_ptr< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_shared_ptr__not_null);
+ATF_TEST_CASE_BODY(std_shared_ptr__not_null)
+{
+ do_check("f", std::shared_ptr< char >(new char('f')));
+ do_check("8", std::shared_ptr< int >(new int(8)));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_set__empty);
+ATF_TEST_CASE_BODY(std_set__empty)
+{
+ do_check("set()", std::set< char >());
+ do_check("set()", std::set< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_set__some);
+ATF_TEST_CASE_BODY(std_set__some)
+{
+ {
+ std::set< char > v;
+ v.insert('b');
+ v.insert('z');
+ do_check("set(b, z)", v);
+ }
+
+ {
+ std::set< int > v;
+ v.insert(5);
+ v.insert(2);
+ v.insert(8);
+ do_check("set(2, 5, 8)", v);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_vector__empty);
+ATF_TEST_CASE_BODY(std_vector__empty)
+{
+ do_check("[]", std::vector< char >());
+ do_check("[]", std::vector< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_vector__some);
+ATF_TEST_CASE_BODY(std_vector__some)
+{
+ {
+ std::vector< char > v;
+ v.push_back('b');
+ v.push_back('z');
+ do_check("[b, z]", v);
+ }
+
+ {
+ std::vector< int > v;
+ v.push_back(5);
+ v.push_back(2);
+ v.push_back(8);
+ do_check("[5, 2, 8]", v);
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, std_map__empty);
+ ATF_ADD_TEST_CASE(tcs, std_map__some);
+
+ ATF_ADD_TEST_CASE(tcs, std_pair);
+
+ ATF_ADD_TEST_CASE(tcs, std_shared_ptr__null);
+ ATF_ADD_TEST_CASE(tcs, std_shared_ptr__not_null);
+
+ ATF_ADD_TEST_CASE(tcs, std_set__empty);
+ ATF_ADD_TEST_CASE(tcs, std_set__some);
+
+ ATF_ADD_TEST_CASE(tcs, std_vector__empty);
+ ATF_ADD_TEST_CASE(tcs, std_vector__some);
+}
diff --git a/utils/format/exceptions.cpp b/utils/format/exceptions.cpp
new file mode 100644
index 000000000000..299b1d23cd8d
--- /dev/null
+++ b/utils/format/exceptions.cpp
@@ -0,0 +1,110 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/format/exceptions.hpp"
+
+using utils::format::bad_format_error;
+using utils::format::error;
+using utils::format::extra_args_error;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new bad_format_error.
+///
+/// \param format_ The invalid format string.
+/// \param message Description of the error in the format string.
+bad_format_error::bad_format_error(const std::string& format_,
+ const std::string& message) :
+ error("Invalid formatting string '" + format_ + "': " + message),
+ _format(format_)
+{
+}
+
+
+/// Destructor for the error.
+bad_format_error::~bad_format_error(void) throw()
+{
+}
+
+
+/// \return The format string that caused the error.
+const std::string&
+bad_format_error::format(void) const
+{
+ return _format;
+}
+
+
+/// Constructs a new extra_args_error.
+///
+/// \param format_ The format string.
+/// \param arg_ The first extra argument passed to the format string.
+extra_args_error::extra_args_error(const std::string& format_,
+ const std::string& arg_) :
+ error("Not enough fields in formatting string '" + format_ + "' to place "
+ "argument '" + arg_ + "'"),
+ _format(format_),
+ _arg(arg_)
+{
+}
+
+
+/// Destructor for the error.
+extra_args_error::~extra_args_error(void) throw()
+{
+}
+
+
+/// \return The format string that was passed too many arguments.
+const std::string&
+extra_args_error::format(void) const
+{
+ return _format;
+}
+
+
+/// \return The first argument that caused the error.
+const std::string&
+extra_args_error::arg(void) const
+{
+ return _arg;
+}
diff --git a/utils/format/exceptions.hpp b/utils/format/exceptions.hpp
new file mode 100644
index 000000000000..a28376df9c08
--- /dev/null
+++ b/utils/format/exceptions.hpp
@@ -0,0 +1,84 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/format/exceptions.hpp
+/// Exception types raised by the format module.
+
+#if !defined(UTILS_FORMAT_EXCEPTIONS_HPP)
+#define UTILS_FORMAT_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace utils {
+namespace format {
+
+
+/// Base exception for format errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ virtual ~error(void) throw();
+};
+
+
+/// Error denoting a bad format string.
+class bad_format_error : public error {
+ /// The format string that caused the error.
+ std::string _format;
+
+public:
+ explicit bad_format_error(const std::string&, const std::string&);
+ virtual ~bad_format_error(void) throw();
+
+ const std::string& format(void) const;
+};
+
+
+/// Error denoting too many arguments for the format string.
+class extra_args_error : public error {
+ /// The format string that was passed too many arguments.
+ std::string _format;
+
+ /// The first argument that caused the error.
+ std::string _arg;
+
+public:
+ explicit extra_args_error(const std::string&, const std::string&);
+ virtual ~extra_args_error(void) throw();
+
+ const std::string& format(void) const;
+ const std::string& arg(void) const;
+};
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_EXCEPTIONS_HPP)
diff --git a/utils/format/exceptions_test.cpp b/utils/format/exceptions_test.cpp
new file mode 100644
index 000000000000..28d401e57dad
--- /dev/null
+++ b/utils/format/exceptions_test.cpp
@@ -0,0 +1,74 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/format/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+using utils::format::bad_format_error;
+using utils::format::error;
+using utils::format::extra_args_error;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bad_format_error);
+ATF_TEST_CASE_BODY(bad_format_error)
+{
+ const bad_format_error e("format-string", "the-error");
+ ATF_REQUIRE(std::strcmp("Invalid formatting string 'format-string': "
+ "the-error", e.what()) == 0);
+ ATF_REQUIRE_EQ("format-string", e.format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(extra_args_error);
+ATF_TEST_CASE_BODY(extra_args_error)
+{
+ const extra_args_error e("fmt", "extra");
+ ATF_REQUIRE(std::strcmp("Not enough fields in formatting string 'fmt' to "
+ "place argument 'extra'", e.what()) == 0);
+ ATF_REQUIRE_EQ("fmt", e.format());
+ ATF_REQUIRE_EQ("extra", e.arg());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, bad_format_error);
+ ATF_ADD_TEST_CASE(tcs, extra_args_error);
+}
diff --git a/utils/format/formatter.cpp b/utils/format/formatter.cpp
new file mode 100644
index 000000000000..99cfd40f03ab
--- /dev/null
+++ b/utils/format/formatter.cpp
@@ -0,0 +1,293 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/format/formatter.hpp"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "utils/format/exceptions.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace format = utils::format;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Finds the next placeholder in a string.
+///
+/// \param format The original format string provided by the user; needed for
+/// error reporting purposes only.
+/// \param expansion The string containing the placeholder to look for. Any
+/// '%%' in the string will be skipped, and they must be stripped later by
+/// strip_double_percent().
+/// \param begin The position from which to start looking for the next
+/// placeholder.
+///
+/// \return The position in the string in which the placeholder is located and
+/// the placeholder itself. If there are no placeholders left, this returns
+/// the length of the string and an empty string.
+///
+/// \throw bad_format_error If the input string contains a trailing formatting
+/// character. We cannot detect any other kind of invalid formatter because
+/// we do not implement a full parser for them.
+static std::pair< std::string::size_type, std::string >
+find_next_placeholder(const std::string& format,
+ const std::string& expansion,
+ std::string::size_type begin)
+{
+ begin = expansion.find('%', begin);
+ while (begin != std::string::npos && expansion[begin + 1] == '%')
+ begin = expansion.find('%', begin + 2);
+ if (begin == std::string::npos)
+ return std::make_pair(expansion.length(), "");
+ if (begin == expansion.length() - 1)
+ throw format::bad_format_error(format, "Trailing %");
+
+ std::string::size_type end = begin + 1;
+ while (end < expansion.length() && expansion[end] != 's')
+ end++;
+ const std::string placeholder = expansion.substr(begin, end - begin + 1);
+ if (end == expansion.length() ||
+ placeholder.find('%', 1) != std::string::npos)
+ throw format::bad_format_error(format, "Unterminated placeholder '" +
+ placeholder + "'");
+ return std::make_pair(begin, placeholder);
+}
+
+
+/// Converts a string to an integer.
+///
+/// \param format The format string; for error reporting purposes only.
+/// \param str The string to conver.
+/// \param what The name of the field this integer belongs to; for error
+/// reporting purposes only.
+///
+/// \return An integer representing the input string.
+inline int
+to_int(const std::string& format, const std::string& str, const char* what)
+{
+ try {
+ return text::to_type< int >(str);
+ } catch (const text::value_error& e) {
+ throw format::bad_format_error(format, "Invalid " + std::string(what) +
+ "specifier");
+ }
+}
+
+
+/// Constructs an std::ostringstream based on a formatting placeholder.
+///
+/// \param format The format placeholder; may be empty.
+///
+/// \return A new std::ostringstream that is prepared to format a single
+/// object in the manner specified by the format placeholder.
+///
+/// \throw bad_format_error If the format string is bad. We do minimal
+/// validation on this string though.
+static std::ostringstream*
+new_ostringstream(const std::string& format)
+{
+ std::auto_ptr< std::ostringstream > output(new std::ostringstream());
+
+ if (format.length() <= 2) {
+ // If the format is empty, we create a new stream so that we don't have
+ // to check for NULLs later on. We rarely should hit this condition
+ // (and when we do it's a bug in the caller), so this is not a big deal.
+ //
+ // Otherwise, if the format is a regular '%s', then we don't have to do
+ // any processing for additional formatters. So this is just a "fast
+ // path".
+ } else {
+ std::string partial = format.substr(1, format.length() - 2);
+ if (partial[0] == '0') {
+ output->fill('0');
+ partial.erase(0, 1);
+ }
+ if (!partial.empty()) {
+ const std::string::size_type dot = partial.find('.');
+ if (dot != 0)
+ output->width(to_int(format, partial.substr(0, dot), "width"));
+ if (dot != std::string::npos) {
+ output->setf(std::ios::fixed, std::ios::floatfield);
+ output->precision(to_int(format, partial.substr(dot + 1),
+ "precision"));
+ }
+ }
+ }
+
+ return output.release();
+}
+
+
+/// Replaces '%%' by '%' in a given string range.
+///
+/// \param in The input string to be rewritten.
+/// \param begin The position at which to start the replacement.
+/// \param end The position at which to end the replacement.
+///
+/// \return The modified string and the amount of characters removed.
+static std::pair< std::string, int >
+strip_double_percent(const std::string& in, const std::string::size_type begin,
+ std::string::size_type end)
+{
+ std::string part = in.substr(begin, end - begin);
+
+ int removed = 0;
+ std::string::size_type pos = part.find("%%");
+ while (pos != std::string::npos) {
+ part.erase(pos, 1);
+ ++removed;
+ pos = part.find("%%", pos + 1);
+ }
+
+ return std::make_pair(in.substr(0, begin) + part + in.substr(end), removed);
+}
+
+
+} // anonymous namespace
+
+
+/// Performs internal initialization of the formatter.
+///
+/// This is separate from the constructor just because it is shared by different
+/// overloaded constructors.
+void
+format::formatter::init(void)
+{
+ const std::pair< std::string::size_type, std::string > placeholder =
+ find_next_placeholder(_format, _expansion, _last_pos);
+ const std::pair< std::string, int > no_percents =
+ strip_double_percent(_expansion, _last_pos, placeholder.first);
+
+ _oss = new_ostringstream(placeholder.second);
+
+ _expansion = no_percents.first;
+ _placeholder_pos = placeholder.first - no_percents.second;
+ _placeholder = placeholder.second;
+}
+
+
+/// Constructs a new formatter object (internal).
+///
+/// \param format The format string.
+/// \param expansion The format string with any replacements performed so far.
+/// \param last_pos The position from which to start looking for formatting
+/// placeholders. This must be maintained in case one of the replacements
+/// introduced a new placeholder, which must be ignored. Think, for
+/// example, replacing a "%s" string with "foo %s".
+format::formatter::formatter(const std::string& format,
+ const std::string& expansion,
+ const std::string::size_type last_pos) :
+ _format(format),
+ _expansion(expansion),
+ _last_pos(last_pos),
+ _oss(NULL)
+{
+ init();
+}
+
+
+/// Constructs a new formatter object.
+///
+/// \param format The format string. The formatters in the string are not
+/// validated during construction, but will cause errors when used later if
+/// they are invalid.
+format::formatter::formatter(const std::string& format) :
+ _format(format),
+ _expansion(format),
+ _last_pos(0),
+ _oss(NULL)
+{
+ init();
+}
+
+
+format::formatter::~formatter(void)
+{
+ delete _oss;
+}
+
+
+/// Returns the formatted string.
+///
+/// \return A string representation of the formatted string.
+const std::string&
+format::formatter::str(void) const
+{
+ return _expansion;
+}
+
+
+/// Automatic conversion of formatter objects to strings.
+///
+/// This is provided to allow painless injection of formatter objects into
+/// streams, without having to manually call the str() method.
+format::formatter::operator const std::string&(void) const
+{
+ return _expansion;
+}
+
+
+/// Specialization of operator% for booleans.
+///
+/// \param value The boolean to inject into the format string.
+///
+/// \return A new formatter that has one less format placeholder.
+format::formatter
+format::formatter::operator%(const bool& value) const
+{
+ (*_oss) << (value ? "true" : "false");
+ return replace(_oss->str());
+}
+
+
+/// Replaces the first formatting placeholder with a value.
+///
+/// \param arg The replacement string.
+///
+/// \return A new formatter in which the first formatting placeholder has been
+/// replaced by arg and is ready to replace the next item.
+///
+/// \throw utils::format::extra_args_error If there are no more formatting
+/// placeholders in the input string, or if the placeholder is invalid.
+format::formatter
+format::formatter::replace(const std::string& arg) const
+{
+ if (_placeholder_pos == _expansion.length())
+ throw format::extra_args_error(_format, arg);
+
+ const std::string expansion = _expansion.substr(0, _placeholder_pos)
+ + arg + _expansion.substr(_placeholder_pos + _placeholder.length());
+ return formatter(_format, expansion, _placeholder_pos + arg.length());
+}
diff --git a/utils/format/formatter.hpp b/utils/format/formatter.hpp
new file mode 100644
index 000000000000..8c6188745a2e
--- /dev/null
+++ b/utils/format/formatter.hpp
@@ -0,0 +1,123 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/format/formatter.hpp
+/// Provides the definition of the utils::format::formatter class.
+///
+/// The utils::format::formatter class is a poor man's replacement for the
+/// Boost.Format library, as it is much simpler and has less dependencies.
+///
+/// Be aware that the formatting supported by this module is NOT compatible
+/// with printf(3) nor with Boost.Format. The general syntax for a
+/// placeholder in a formatting string is:
+///
+/// %[0][width][.precision]s
+///
+/// In particular, note that the only valid formatting specifier is %s: the
+/// library deduces what to print based on the type of the variable passed
+/// in, not based on what the format string says. Also, note that the only
+/// valid padding character is 0.
+
+#if !defined(UTILS_FORMAT_FORMATTER_HPP)
+#define UTILS_FORMAT_FORMATTER_HPP
+
+#include "utils/format/formatter_fwd.hpp"
+
+#include <sstream>
+#include <string>
+
+namespace utils {
+namespace format {
+
+
+/// Mechanism to format strings similar to printf.
+///
+/// A formatter always maintains the original format string but also holds a
+/// partial expansion. The partial expansion is immutable in the context of a
+/// formatter instance, but calls to operator% return new formatter objects with
+/// one less formatting placeholder.
+///
+/// In general, one can format a string in the following manner:
+///
+/// \code
+/// const std::string s = (formatter("%s %s") % "foo" % 5).str();
+/// \endcode
+///
+/// which, following the explanation above, would correspond to:
+///
+/// \code
+/// const formatter f1("%s %s");
+/// const formatter f2 = f1 % "foo";
+/// const formatter f3 = f2 % 5;
+/// const std::string s = f3.str();
+/// \endcode
+class formatter {
+ /// The original format string provided by the user.
+ std::string _format;
+
+ /// The current "expansion" of the format string.
+ ///
+ /// This field gets updated on every call to operator%() to have one less
+ /// formatting placeholder.
+ std::string _expansion;
+
+ /// The position of _expansion from which to scan for placeholders.
+ std::string::size_type _last_pos;
+
+ /// The position of the first placeholder in the current expansion.
+ std::string::size_type _placeholder_pos;
+
+ /// The first placeholder in the current expansion.
+ std::string _placeholder;
+
+ /// Stream used to format any possible argument supplied by operator%().
+ std::ostringstream* _oss;
+
+ formatter replace(const std::string&) const;
+
+ void init(void);
+ formatter(const std::string&, const std::string&,
+ const std::string::size_type);
+
+public:
+ explicit formatter(const std::string&);
+ ~formatter(void);
+
+ const std::string& str(void) const;
+ operator const std::string&(void) const;
+
+ template< typename Type > formatter operator%(const Type&) const;
+ formatter operator%(const bool&) const;
+};
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_FORMATTER_HPP)
diff --git a/utils/format/formatter.ipp b/utils/format/formatter.ipp
new file mode 100644
index 000000000000..6fad024b704f
--- /dev/null
+++ b/utils/format/formatter.ipp
@@ -0,0 +1,76 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_FORMAT_FORMATTER_IPP)
+#define UTILS_FORMAT_FORMATTER_IPP
+
+#include <ostream>
+
+#include "utils/format/formatter.hpp"
+
+namespace utils {
+namespace format {
+
+
+/// Replaces the first format placeholder in a formatter.
+///
+/// Constructs a new formatter object that has one less formatting placeholder,
+/// as this has been replaced by the provided argument. Calling this operator
+/// N times, where N is the number of formatting placeholders, effectively
+/// formats the string.
+///
+/// \param arg The argument to use as replacement for the format placeholder.
+///
+/// \return A new formatter that has one less format placeholder.
+template< typename Type >
+inline formatter
+formatter::operator%(const Type& arg) const
+{
+ (*_oss) << arg;
+ return replace(_oss->str());
+}
+
+
+/// Inserts a formatter string into a stream.
+///
+/// \param os The output stream.
+/// \param f The formatter to process and inject into the stream.
+///
+/// \return The output stream os.
+inline std::ostream&
+operator<<(std::ostream& os, const formatter& f)
+{
+ return (os << f.str());
+}
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_FORMATTER_IPP)
diff --git a/utils/format/formatter_fwd.hpp b/utils/format/formatter_fwd.hpp
new file mode 100644
index 000000000000..72c9e5ebf196
--- /dev/null
+++ b/utils/format/formatter_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/format/formatter_fwd.hpp
+/// Forward declarations for utils/format/formatter.hpp
+
+#if !defined(UTILS_FORMAT_FORMATTER_FWD_HPP)
+#define UTILS_FORMAT_FORMATTER_FWD_HPP
+
+namespace utils {
+namespace format {
+
+
+class formatter;
+
+
+} // namespace format
+} // namespace utils
+
+#endif // !defined(UTILS_FORMAT_FORMATTER_FWD_HPP)
diff --git a/utils/format/formatter_test.cpp b/utils/format/formatter_test.cpp
new file mode 100644
index 000000000000..fdae785b1db7
--- /dev/null
+++ b/utils/format/formatter_test.cpp
@@ -0,0 +1,265 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/format/formatter.hpp"
+
+#include <ostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/exceptions.hpp"
+#include "utils/format/macros.hpp"
+
+namespace format = utils::format;
+
+
+namespace {
+
+
+/// Wraps an integer in a C++ class.
+///
+/// This custom type exists to ensure that we can feed arbitrary objects that
+/// support operator<< to the formatter;
+class int_wrapper {
+ /// The wrapped integer.
+ int _value;
+
+public:
+ /// Constructs a new wrapper.
+ ///
+ /// \param value_ The value to wrap.
+ int_wrapper(const int value_) : _value(value_)
+ {
+ }
+
+ /// Returns the wrapped value.
+ ///
+ /// \return An integer.
+ int
+ value(void) const
+ {
+ return _value;
+ }
+};
+
+
+/// Writes a wrapped integer into an output stream.
+///
+/// \param output The output stream into which to place the integer.
+/// \param wrapper The wrapped integer.
+///
+/// \return The output stream.
+std::ostream&
+operator<<(std::ostream& output, const int_wrapper& wrapper)
+{
+ return (output << wrapper.value());
+}
+
+
+} // anonymous namespace
+
+
+/// Calls ATF_REQUIRE_EQ on an expected string and a formatter.
+///
+/// This is pure syntactic sugar to avoid calling the str() method on all the
+/// individual tests below, which results in very long lines that require
+/// wrapping and clutter readability.
+///
+/// \param expected The expected string generated by the formatter.
+/// \param formatter The formatter to test.
+#define EQ(expected, formatter) ATF_REQUIRE_EQ(expected, (formatter).str())
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_fields);
+ATF_TEST_CASE_BODY(no_fields)
+{
+ EQ("Plain string", F("Plain string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(one_field);
+ATF_TEST_CASE_BODY(one_field)
+{
+ EQ("foo", F("%sfoo") % "");
+ EQ(" foo", F("%sfoo") % " ");
+ EQ("foo ", F("foo %s") % "");
+ EQ("foo bar", F("foo %s") % "bar");
+ EQ("foo bar baz", F("foo %s baz") % "bar");
+ EQ("foo %s %s", F("foo %s %s") % "%s" % "%s");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(many_fields);
+ATF_TEST_CASE_BODY(many_fields)
+{
+ EQ("", F("%s%s") % "" % "");
+ EQ("foo", F("%s%s%s") % "" % "foo" % "");
+ EQ("some 5 text", F("%s %s %s") % "some" % 5 % "text");
+ EQ("f%s 5 text", F("%s %s %s") % "f%s" % 5 % "text");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape);
+ATF_TEST_CASE_BODY(escape)
+{
+ EQ("%", F("%%"));
+ EQ("% %", F("%% %%"));
+ EQ("%% %%", F("%%%% %%%%"));
+
+ EQ("foo %", F("foo %%"));
+ EQ("foo bar %", F("foo %s %%") % "bar");
+ EQ("foo % bar", F("foo %% %s") % "bar");
+
+ EQ("foo %%", F("foo %s") % "%%");
+ EQ("foo a%%b", F("foo a%sb") % "%%");
+ EQ("foo a%%b", F("foo %s") % "a%%b");
+
+ EQ("foo % bar %%", F("foo %% %s %%%%") % "bar");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(extra_args_error);
+ATF_TEST_CASE_BODY(extra_args_error)
+{
+ using format::extra_args_error;
+
+ ATF_REQUIRE_THROW(extra_args_error, F("foo") % "bar");
+ ATF_REQUIRE_THROW(extra_args_error, F("foo %%") % "bar");
+ ATF_REQUIRE_THROW(extra_args_error, F("foo %s") % "bar" % "baz");
+ ATF_REQUIRE_THROW(extra_args_error, F("foo %s") % "%s" % "bar");
+ ATF_REQUIRE_THROW(extra_args_error, F("%s foo %s") % "bar" % "baz" % "foo");
+
+ try {
+ F("foo %s %s") % "bar" % "baz" % "something extra";
+ fail("extra_args_error not raised");
+ } catch (const extra_args_error& e) {
+ ATF_REQUIRE_EQ("foo %s %s", e.format());
+ ATF_REQUIRE_EQ("something extra", e.arg());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__class);
+ATF_TEST_CASE_BODY(format__class)
+{
+ EQ("foo bar", F("%s") % std::string("foo bar"));
+ EQ("3", F("%s") % int_wrapper(3));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__pointer);
+ATF_TEST_CASE_BODY(format__pointer)
+{
+ EQ("0xcafebabe", F("%s") % reinterpret_cast< void* >(0xcafebabe));
+ EQ("foo bar", F("%s") % "foo bar");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__bool);
+ATF_TEST_CASE_BODY(format__bool)
+{
+ EQ("true", F("%s") % true);
+ EQ("false", F("%s") % false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__char);
+ATF_TEST_CASE_BODY(format__char)
+{
+ EQ("Z", F("%s") % 'Z');
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__float);
+ATF_TEST_CASE_BODY(format__float)
+{
+ EQ("3", F("%s") % 3.0);
+ EQ("3.0", F("%.1s") % 3.0);
+ EQ("3.0", F("%0.1s") % 3.0);
+ EQ(" 15.600", F("%8.3s") % 15.6);
+ EQ("01.52", F("%05.2s") % 1.52);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__int);
+ATF_TEST_CASE_BODY(format__int)
+{
+ EQ("3", F("%s") % 3);
+ EQ("3", F("%0s") % 3);
+ EQ(" -123", F("%5s") % -123);
+ EQ("00078", F("%05s") % 78);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(format__error);
+ATF_TEST_CASE_BODY(format__error)
+{
+ using format::bad_format_error;
+
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Trailing %", F("%"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Trailing %", F("f%"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Trailing %", F("f%%%"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Trailing %", F("ab %s cd%") % "cd");
+
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Invalid width", F("%1bs"));
+
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Invalid precision", F("%.s"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Invalid precision", F("%0.s"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Invalid precision", F("%123.s"));
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Invalid precision", F("%.12bs"));
+
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Unterminated", F("%c") % 'Z');
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Unterminated", F("%d") % 5);
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Unterminated", F("%.1f") % 3);
+ ATF_REQUIRE_THROW_RE(bad_format_error, "Unterminated", F("%d%s") % 3 % "a");
+
+ try {
+ F("foo %s%") % "bar";
+ fail("bad_format_error not raised");
+ } catch (const bad_format_error& e) {
+ ATF_REQUIRE_EQ("foo %s%", e.format());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, no_fields);
+ ATF_ADD_TEST_CASE(tcs, one_field);
+ ATF_ADD_TEST_CASE(tcs, many_fields);
+ ATF_ADD_TEST_CASE(tcs, escape);
+ ATF_ADD_TEST_CASE(tcs, extra_args_error);
+
+ ATF_ADD_TEST_CASE(tcs, format__class);
+ ATF_ADD_TEST_CASE(tcs, format__pointer);
+ ATF_ADD_TEST_CASE(tcs, format__bool);
+ ATF_ADD_TEST_CASE(tcs, format__char);
+ ATF_ADD_TEST_CASE(tcs, format__float);
+ ATF_ADD_TEST_CASE(tcs, format__int);
+ ATF_ADD_TEST_CASE(tcs, format__error);
+}
diff --git a/utils/format/macros.hpp b/utils/format/macros.hpp
new file mode 100644
index 000000000000..09ef14ea485e
--- /dev/null
+++ b/utils/format/macros.hpp
@@ -0,0 +1,58 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/format/macros.hpp
+/// Convenience macros to simplify usage of the format library.
+///
+/// This file <em>must not be included from other header files</em>.
+
+#if !defined(UTILS_FORMAT_MACROS_HPP)
+#define UTILS_FORMAT_MACROS_HPP
+
+// We include the .ipp file instead of .hpp because, after all, macros.hpp
+// is provided purely for convenience and must not be included from other
+// header files. Henceforth, we make things easier to the callers.
+#include "utils/format/formatter.ipp"
+
+
+/// Constructs a utils::format::formatter object with the given format string.
+///
+/// This macro is just a wrapper to make the construction of
+/// utils::format::formatter objects shorter, and thus to allow inlining these
+/// calls right in where formatted strings are required. A typical usage would
+/// look like:
+///
+/// \code
+/// std::cout << F("%s %d\n") % my_str % my_int;
+/// \endcode
+///
+/// \param fmt The format string.
+#define F(fmt) utils::format::formatter(fmt)
+
+
+#endif // !defined(UTILS_FORMAT_MACROS_HPP)
diff --git a/utils/fs/Kyuafile b/utils/fs/Kyuafile
new file mode 100644
index 000000000000..66cb918fca92
--- /dev/null
+++ b/utils/fs/Kyuafile
@@ -0,0 +1,10 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="auto_cleaners_test"}
+atf_test_program{name="directory_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="lua_module_test"}
+atf_test_program{name="operations_test"}
+atf_test_program{name="path_test"}
diff --git a/utils/fs/Makefile.am.inc b/utils/fs/Makefile.am.inc
new file mode 100644
index 000000000000..2acdadafa79b
--- /dev/null
+++ b/utils/fs/Makefile.am.inc
@@ -0,0 +1,84 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(LUTOK_CFLAGS)
+UTILS_LIBS += $(LUTOK_LIBS)
+
+libutils_a_CPPFLAGS += $(LUTOK_CFLAGS)
+libutils_a_SOURCES += utils/fs/auto_cleaners.cpp
+libutils_a_SOURCES += utils/fs/auto_cleaners.hpp
+libutils_a_SOURCES += utils/fs/auto_cleaners_fwd.hpp
+libutils_a_SOURCES += utils/fs/directory.cpp
+libutils_a_SOURCES += utils/fs/directory.hpp
+libutils_a_SOURCES += utils/fs/directory_fwd.hpp
+libutils_a_SOURCES += utils/fs/exceptions.cpp
+libutils_a_SOURCES += utils/fs/exceptions.hpp
+libutils_a_SOURCES += utils/fs/lua_module.cpp
+libutils_a_SOURCES += utils/fs/lua_module.hpp
+libutils_a_SOURCES += utils/fs/operations.cpp
+libutils_a_SOURCES += utils/fs/operations.hpp
+libutils_a_SOURCES += utils/fs/path.cpp
+libutils_a_SOURCES += utils/fs/path.hpp
+libutils_a_SOURCES += utils/fs/path_fwd.hpp
+
+if WITH_ATF
+tests_utils_fsdir = $(pkgtestsdir)/utils/fs
+
+tests_utils_fs_DATA = utils/fs/Kyuafile
+EXTRA_DIST += $(tests_utils_fs_DATA)
+
+tests_utils_fs_PROGRAMS = utils/fs/auto_cleaners_test
+utils_fs_auto_cleaners_test_SOURCES = utils/fs/auto_cleaners_test.cpp
+utils_fs_auto_cleaners_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_auto_cleaners_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_fs_PROGRAMS += utils/fs/directory_test
+utils_fs_directory_test_SOURCES = utils/fs/directory_test.cpp
+utils_fs_directory_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_directory_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_fs_PROGRAMS += utils/fs/exceptions_test
+utils_fs_exceptions_test_SOURCES = utils/fs/exceptions_test.cpp
+utils_fs_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_fs_PROGRAMS += utils/fs/lua_module_test
+utils_fs_lua_module_test_SOURCES = utils/fs/lua_module_test.cpp
+utils_fs_lua_module_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_lua_module_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_fs_PROGRAMS += utils/fs/operations_test
+utils_fs_operations_test_SOURCES = utils/fs/operations_test.cpp
+utils_fs_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_fs_PROGRAMS += utils/fs/path_test
+utils_fs_path_test_SOURCES = utils/fs/path_test.cpp
+utils_fs_path_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_fs_path_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/fs/auto_cleaners.cpp b/utils/fs/auto_cleaners.cpp
new file mode 100644
index 000000000000..94ef94465e57
--- /dev/null
+++ b/utils/fs/auto_cleaners.cpp
@@ -0,0 +1,261 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/auto_cleaners.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/interrupts.hpp"
+
+namespace fs = utils::fs;
+namespace signals = utils::signals;
+
+
+/// Shared implementation of the auto_directory.
+struct utils::fs::auto_directory::impl : utils::noncopyable {
+ /// The path to the directory being managed.
+ fs::path _directory;
+
+ /// Whether cleanup() has been already executed or not.
+ bool _cleaned;
+
+ /// Constructor.
+ ///
+ /// \param directory_ The directory to grab the ownership of.
+ impl(const path& directory_) :
+ _directory(directory_),
+ _cleaned(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ try {
+ this->cleanup();
+ } catch (const fs::error& e) {
+ LW(F("Failed to auto-cleanup directory '%s': %s") % _directory %
+ e.what());
+ }
+ }
+
+ /// Removes the directory.
+ ///
+ /// See the cleanup() method of the auto_directory class for details.
+ void
+ cleanup(void)
+ {
+ if (!_cleaned) {
+ // Mark this as cleaned first so that, in case of failure, we don't
+ // reraise the error from the destructor.
+ _cleaned = true;
+
+ fs::rmdir(_directory);
+ }
+ }
+};
+
+
+/// Constructs a new auto_directory and grabs ownership of a directory.
+///
+/// \param directory_ The directory to grab the ownership of.
+fs::auto_directory::auto_directory(const path& directory_) :
+ _pimpl(new impl(directory_))
+{
+}
+
+
+/// Deletes the managed directory; must be empty.
+///
+/// This should not be relied on because it cannot provide proper error
+/// reporting. Instead, the caller should use the cleanup() method.
+fs::auto_directory::~auto_directory(void)
+{
+}
+
+
+/// Creates a self-destructing temporary directory.
+///
+/// See the notes for fs::mkdtemp_public() for details on the permissions
+/// given to the temporary directory, which are looser than what the standard
+/// mkdtemp would grant.
+///
+/// \param path_template The template for the temporary path, which is a
+/// basename that is created within the TMPDIR. Must contain the XXXXXX
+/// pattern, which is atomically replaced by a random unique string.
+///
+/// \return The self-destructing directory.
+///
+/// \throw fs::error If the creation fails.
+fs::auto_directory
+fs::auto_directory::mkdtemp_public(const std::string& path_template)
+{
+ signals::interrupts_inhibiter inhibiter;
+ const fs::path directory_ = fs::mkdtemp_public(path_template);
+ try {
+ return auto_directory(directory_);
+ } catch (...) {
+ fs::rmdir(directory_);
+ throw;
+ }
+}
+
+
+/// Gets the directory managed by this auto_directory.
+///
+/// \return The path to the managed directory.
+const fs::path&
+fs::auto_directory::directory(void) const
+{
+ return _pimpl->_directory;
+}
+
+
+/// Deletes the managed directory; must be empty.
+///
+/// This operation is idempotent.
+///
+/// \throw fs::error If there is a problem removing any directory or file.
+void
+fs::auto_directory::cleanup(void)
+{
+ _pimpl->cleanup();
+}
+
+
+/// Shared implementation of the auto_file.
+struct utils::fs::auto_file::impl : utils::noncopyable {
+ /// The path to the file being managed.
+ fs::path _file;
+
+ /// Whether removed() has been already executed or not.
+ bool _removed;
+
+ /// Constructor.
+ ///
+ /// \param file_ The file to grab the ownership of.
+ impl(const path& file_) :
+ _file(file_),
+ _removed(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ try {
+ this->remove();
+ } catch (const fs::error& e) {
+ LW(F("Failed to auto-cleanup file '%s': %s") % _file %
+ e.what());
+ }
+ }
+
+ /// Removes the file.
+ ///
+ /// See the remove() method of the auto_file class for details.
+ void
+ remove(void)
+ {
+ if (!_removed) {
+ // Mark this as cleaned first so that, in case of failure, we don't
+ // reraise the error from the destructor.
+ _removed = true;
+
+ fs::unlink(_file);
+ }
+ }
+};
+
+
+/// Constructs a new auto_file and grabs ownership of a file.
+///
+/// \param file_ The file to grab the ownership of.
+fs::auto_file::auto_file(const path& file_) :
+ _pimpl(new impl(file_))
+{
+}
+
+
+/// Deletes the managed file.
+///
+/// This should not be relied on because it cannot provide proper error
+/// reporting. Instead, the caller should use the remove() method.
+fs::auto_file::~auto_file(void)
+{
+}
+
+
+/// Creates a self-destructing temporary file.
+///
+/// \param path_template The template for the temporary path, which is a
+/// basename that is created within the TMPDIR. Must contain the XXXXXX
+/// pattern, which is atomically replaced by a random unique string.
+///
+/// \return The self-destructing file.
+///
+/// \throw fs::error If the creation fails.
+fs::auto_file
+fs::auto_file::mkstemp(const std::string& path_template)
+{
+ signals::interrupts_inhibiter inhibiter;
+ const fs::path file_ = fs::mkstemp(path_template);
+ try {
+ return auto_file(file_);
+ } catch (...) {
+ fs::unlink(file_);
+ throw;
+ }
+}
+
+
+/// Gets the file managed by this auto_file.
+///
+/// \return The path to the managed file.
+const fs::path&
+fs::auto_file::file(void) const
+{
+ return _pimpl->_file;
+}
+
+
+/// Deletes the managed file.
+///
+/// This operation is idempotent.
+///
+/// \throw fs::error If there is a problem removing the file.
+void
+fs::auto_file::remove(void)
+{
+ _pimpl->remove();
+}
diff --git a/utils/fs/auto_cleaners.hpp b/utils/fs/auto_cleaners.hpp
new file mode 100644
index 000000000000..f3e6937e3cea
--- /dev/null
+++ b/utils/fs/auto_cleaners.hpp
@@ -0,0 +1,89 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/auto_cleaners.hpp
+/// RAII wrappers to automatically remove file system entries.
+
+#if !defined(UTILS_FS_AUTO_CLEANERS_HPP)
+#define UTILS_FS_AUTO_CLEANERS_HPP
+
+#include "utils/fs/auto_cleaners_fwd.hpp"
+
+#include <memory>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace fs {
+
+
+/// Grabs ownership of a directory and removes it upon destruction.
+///
+/// This class is reference-counted and therefore only the destruction of the
+/// last instance will cause the removal of the directory.
+class auto_directory {
+ struct impl;
+ /// Reference-counted, shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+public:
+ explicit auto_directory(const path&);
+ ~auto_directory(void);
+
+ static auto_directory mkdtemp_public(const std::string&);
+
+ const path& directory(void) const;
+ void cleanup(void);
+};
+
+
+/// Grabs ownership of a file and removes it upon destruction.
+///
+/// This class is reference-counted and therefore only the destruction of the
+/// last instance will cause the removal of the file.
+class auto_file {
+ struct impl;
+ /// Reference-counted, shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+public:
+ explicit auto_file(const path&);
+ ~auto_file(void);
+
+ static auto_file mkstemp(const std::string&);
+
+ const path& file(void) const;
+ void remove(void);
+};
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_AUTO_CLEANERS_HPP)
diff --git a/utils/fs/auto_cleaners_fwd.hpp b/utils/fs/auto_cleaners_fwd.hpp
new file mode 100644
index 000000000000..c0cfa6333a1a
--- /dev/null
+++ b/utils/fs/auto_cleaners_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/auto_cleaners_fwd.hpp
+/// Forward declarations for utils/fs/auto_cleaners.hpp
+
+#if !defined(UTILS_FS_AUTO_CLEANERS_FWD_HPP)
+#define UTILS_FS_AUTO_CLEANERS_FWD_HPP
+
+namespace utils {
+namespace fs {
+
+
+class auto_directory;
+class auto_file;
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_AUTO_CLEANERS_FWD_HPP)
diff --git a/utils/fs/auto_cleaners_test.cpp b/utils/fs/auto_cleaners_test.cpp
new file mode 100644
index 000000000000..da4bbeb2da68
--- /dev/null
+++ b/utils/fs/auto_cleaners_test.cpp
@@ -0,0 +1,167 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/auto_cleaners.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <atf-c++.hpp>
+
+#include "utils/env.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+
+namespace fs = utils::fs;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_directory__automatic);
+ATF_TEST_CASE_BODY(auto_directory__automatic)
+{
+ const fs::path root("root");
+ fs::mkdir(root, 0755);
+
+ {
+ fs::auto_directory dir(root);
+ ATF_REQUIRE_EQ(root, dir.directory());
+
+ ATF_REQUIRE(::access("root", X_OK) == 0);
+
+ {
+ fs::auto_directory dir_copy(dir);
+ }
+ // Should still exist after a copy is destructed.
+ ATF_REQUIRE(::access("root", X_OK) == 0);
+ }
+ ATF_REQUIRE(::access("root", X_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_directory__explicit);
+ATF_TEST_CASE_BODY(auto_directory__explicit)
+{
+ const fs::path root("root");
+ fs::mkdir(root, 0755);
+
+ fs::auto_directory dir(root);
+ ATF_REQUIRE_EQ(root, dir.directory());
+
+ ATF_REQUIRE(::access("root", X_OK) == 0);
+ dir.cleanup();
+ dir.cleanup();
+ ATF_REQUIRE(::access("root", X_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_directory__mkdtemp_public);
+ATF_TEST_CASE_BODY(auto_directory__mkdtemp_public)
+{
+ utils::setenv("TMPDIR", (fs::current_path() / "tmp").str());
+ fs::mkdir(fs::path("tmp"), 0755);
+
+ const std::string path_template("test.XXXXXX");
+ {
+ fs::auto_directory auto_directory = fs::auto_directory::mkdtemp_public(
+ path_template);
+ ATF_REQUIRE(::access((fs::path("tmp") / path_template).c_str(),
+ X_OK) == -1);
+ ATF_REQUIRE(::rmdir("tmp") == -1);
+
+ ATF_REQUIRE(::access(auto_directory.directory().c_str(), X_OK) == 0);
+ }
+ ATF_REQUIRE(::rmdir("tmp") != -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_file__automatic);
+ATF_TEST_CASE_BODY(auto_file__automatic)
+{
+ const fs::path file("foo");
+ atf::utils::create_file(file.str(), "");
+ {
+ fs::auto_file auto_file(file);
+ ATF_REQUIRE_EQ(file, auto_file.file());
+
+ ATF_REQUIRE(::access(file.c_str(), R_OK) == 0);
+
+ {
+ fs::auto_file auto_file_copy(auto_file);
+ }
+ // Should still exist after a copy is destructed.
+ ATF_REQUIRE(::access(file.c_str(), R_OK) == 0);
+ }
+ ATF_REQUIRE(::access(file.c_str(), R_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_file__explicit);
+ATF_TEST_CASE_BODY(auto_file__explicit)
+{
+ const fs::path file("bar");
+ atf::utils::create_file(file.str(), "");
+
+ fs::auto_file auto_file(file);
+ ATF_REQUIRE_EQ(file, auto_file.file());
+
+ ATF_REQUIRE(::access(file.c_str(), R_OK) == 0);
+ auto_file.remove();
+ auto_file.remove();
+ ATF_REQUIRE(::access(file.c_str(), R_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(auto_file__mkstemp);
+ATF_TEST_CASE_BODY(auto_file__mkstemp)
+{
+ utils::setenv("TMPDIR", (fs::current_path() / "tmp").str());
+ fs::mkdir(fs::path("tmp"), 0755);
+
+ const std::string path_template("test.XXXXXX");
+ {
+ fs::auto_file auto_file = fs::auto_file::mkstemp(path_template);
+ ATF_REQUIRE(::access((fs::path("tmp") / path_template).c_str(),
+ X_OK) == -1);
+ ATF_REQUIRE(::rmdir("tmp") == -1);
+
+ ATF_REQUIRE(::access(auto_file.file().c_str(), R_OK) == 0);
+ }
+ ATF_REQUIRE(::rmdir("tmp") != -1);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, auto_directory__automatic);
+ ATF_ADD_TEST_CASE(tcs, auto_directory__explicit);
+ ATF_ADD_TEST_CASE(tcs, auto_directory__mkdtemp_public);
+
+ ATF_ADD_TEST_CASE(tcs, auto_file__automatic);
+ ATF_ADD_TEST_CASE(tcs, auto_file__explicit);
+ ATF_ADD_TEST_CASE(tcs, auto_file__mkstemp);
+}
diff --git a/utils/fs/directory.cpp b/utils/fs/directory.cpp
new file mode 100644
index 000000000000..ff7ad5e34357
--- /dev/null
+++ b/utils/fs/directory.cpp
@@ -0,0 +1,360 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/directory.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <dirent.h>
+}
+
+#include <cerrno>
+#include <memory>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/operations.ipp"
+
+namespace detail = utils::fs::detail;
+namespace fs = utils::fs;
+namespace text = utils::text;
+
+
+/// Constructs a new directory entry.
+///
+/// \param name_ Name of the directory entry.
+fs::directory_entry::directory_entry(const std::string& name_) : name(name_)
+{
+}
+
+
+/// Checks if two directory entries are equal.
+///
+/// \param other The entry to compare to.
+///
+/// \return True if the two entries are equal; false otherwise.
+bool
+fs::directory_entry::operator==(const directory_entry& other) const
+{
+ return name == other.name;
+}
+
+
+/// Checks if two directory entries are different.
+///
+/// \param other The entry to compare to.
+///
+/// \return True if the two entries are different; false otherwise.
+bool
+fs::directory_entry::operator!=(const directory_entry& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Checks if this entry sorts before another entry.
+///
+/// \param other The entry to compare to.
+///
+/// \return True if this entry sorts before the other entry; false otherwise.
+bool
+fs::directory_entry::operator<(const directory_entry& other) const
+{
+ return name < other.name;
+}
+
+
+/// Formats a directory entry.
+///
+/// \param output Stream into which to inject the formatted entry.
+/// \param entry The entry to format.
+///
+/// \return A reference to output.
+std::ostream&
+fs::operator<<(std::ostream& output, const directory_entry& entry)
+{
+ output << F("directory_entry{name=%s}") % text::quote(entry.name, '\'');
+ return output;
+}
+
+
+/// Internal implementation details for the directory_iterator.
+///
+/// In order to support multiple concurrent iterators over the same directory
+/// object, this class is the one that performs all directory-level accesses.
+/// In particular, even if it may seem surprising, this is the class that
+/// handles the DIR object for the directory.
+///
+/// Note that iterators implemented by this class do not rely on the container
+/// directory class at all. This should not be relied on for object lifecycle
+/// purposes.
+struct utils::fs::detail::directory_iterator::impl : utils::noncopyable {
+ /// Path of the directory accessed by this iterator.
+ const fs::path _path;
+
+ /// Raw pointer to the system representation of the directory.
+ ///
+ /// We also use this to determine if the iterator is valid (at the end) or
+ /// not. A null pointer means an invalid iterator.
+ ::DIR* _dirp;
+
+ /// Raw representation of the system directory entry.
+ ///
+ /// We need to keep this at the class level so that we can use the
+ /// readdir_r(3) function.
+ ::dirent _dirent;
+
+ /// Custom representation of the directory entry.
+ ///
+ /// This is separate from _dirent because this is the type we return to the
+ /// user. We must keep this as a pointer so that we can support the common
+ /// operators (* and ->) over iterators.
+ std::auto_ptr< directory_entry > _entry;
+
+ /// Constructs an iterator pointing to the "end" of the directory.
+ impl(void) : _path("invalid-directory-entry"), _dirp(NULL)
+ {
+ }
+
+ /// Constructs a new iterator to start scanning a directory.
+ ///
+ /// \param path The directory that will be scanned.
+ ///
+ /// \throw system_error If there is a problem opening the directory.
+ explicit impl(const path& path) : _path(path)
+ {
+ DIR* dirp = ::opendir(_path.c_str());
+ if (dirp == NULL) {
+ const int original_errno = errno;
+ throw fs::system_error(F("opendir(%s) failed") % _path,
+ original_errno);
+ }
+ _dirp = dirp;
+
+ // Initialize our first directory entry. Note that this may actually
+ // close the directory we just opened if the directory happens to be
+ // empty -- but directories are never empty because they at least have
+ // '.' and '..' entries.
+ next();
+ }
+
+ /// Destructor.
+ ///
+ /// This closes the directory if still open.
+ ~impl(void)
+ {
+ if (_dirp != NULL)
+ close();
+ }
+
+ /// Closes the directory and invalidates the iterator.
+ void
+ close(void)
+ {
+ PRE(_dirp != NULL);
+ if (::closedir(_dirp) == -1) {
+ UNREACHABLE_MSG("Invalid dirp provided to closedir(3)");
+ }
+ _dirp = NULL;
+ }
+
+ /// Advances the directory entry to the next one.
+ ///
+ /// It is possible to use this function on a new directory_entry object to
+ /// initialize the first entry.
+ ///
+ /// \throw system_error If the call to readdir_r fails.
+ void
+ next(void)
+ {
+ ::dirent* result;
+
+ if (::readdir_r(_dirp, &_dirent, &result) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("readdir_r(%s) failed") % _path,
+ original_errno);
+ }
+ if (result == NULL) {
+ _entry.reset(NULL);
+ close();
+ } else {
+ _entry.reset(new directory_entry(_dirent.d_name));
+ }
+ }
+};
+
+
+/// Constructs a new directory iterator.
+///
+/// \param pimpl The constructed internal implementation structure to use.
+detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) :
+ _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+detail::directory_iterator::~directory_iterator(void)
+{
+}
+
+
+/// Creates a new directory iterator for a directory.
+///
+/// \return The directory iterator. Note that the result may be invalid.
+///
+/// \throw system_error If opening the directory or reading its first entry
+/// fails.
+detail::directory_iterator
+detail::directory_iterator::new_begin(const path& path)
+{
+ return directory_iterator(std::shared_ptr< impl >(new impl(path)));
+}
+
+
+/// Creates a new invalid directory iterator.
+///
+/// \return The invalid directory iterator.
+detail::directory_iterator
+detail::directory_iterator::new_end(void)
+{
+ return directory_iterator(std::shared_ptr< impl >(new impl()));
+}
+
+
+/// Checks if two iterators are equal.
+///
+/// We consider two iterators to be equal if both of them are invalid or,
+/// otherwise, if they have the exact same internal representation (as given by
+/// equality of the pimpl pointers).
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two iterators are equal; false otherwise.
+bool
+detail::directory_iterator::operator==(const directory_iterator& other) const
+{
+ return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) ||
+ _pimpl == other._pimpl;
+}
+
+
+/// Checks if two iterators are different.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two iterators are different; false otherwise.
+bool
+detail::directory_iterator::operator!=(const directory_iterator& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Moves the iterator one element forward.
+///
+/// \return A reference to the iterator.
+///
+/// \throw system_error If advancing the iterator fails.
+detail::directory_iterator&
+detail::directory_iterator::operator++(void)
+{
+ _pimpl->next();
+ return *this;
+}
+
+
+/// Dereferences the iterator to its contents.
+///
+/// \return A reference to the directory entry pointed to by the iterator.
+const fs::directory_entry&
+detail::directory_iterator::operator*(void) const
+{
+ PRE(_pimpl->_entry.get() != NULL);
+ return *_pimpl->_entry;
+}
+
+
+/// Dereferences the iterator to its contents.
+///
+/// \return A pointer to the directory entry pointed to by the iterator.
+const fs::directory_entry*
+detail::directory_iterator::operator->(void) const
+{
+ PRE(_pimpl->_entry.get() != NULL);
+ return _pimpl->_entry.get();
+}
+
+
+/// Internal implementation details for the directory.
+struct utils::fs::directory::impl : utils::noncopyable {
+ /// Path to the directory to scan.
+ fs::path _path;
+
+ /// Constructs a new directory.
+ ///
+ /// \param path_ Path to the directory to scan.
+ impl(const fs::path& path_) : _path(path_)
+ {
+ }
+};
+
+
+/// Constructs a new directory.
+///
+/// \param path_ Path to the directory to scan.
+fs::directory::directory(const path& path_) : _pimpl(new impl(path_))
+{
+}
+
+
+/// Returns an iterator to start scanning the directory.
+///
+/// \return An iterator on the directory.
+///
+/// \throw system_error If the directory cannot be opened to obtain its first
+/// entry.
+fs::directory::const_iterator
+fs::directory::begin(void) const
+{
+ return const_iterator::new_begin(_pimpl->_path);
+}
+
+
+/// Returns an invalid iterator to check for the end of an scan.
+///
+/// \return An invalid iterator.
+fs::directory::const_iterator
+fs::directory::end(void) const
+{
+ return const_iterator::new_end();
+}
diff --git a/utils/fs/directory.hpp b/utils/fs/directory.hpp
new file mode 100644
index 000000000000..53c37ec86450
--- /dev/null
+++ b/utils/fs/directory.hpp
@@ -0,0 +1,120 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/directory.hpp
+/// Provides the utils::fs::directory class.
+
+#if !defined(UTILS_FS_DIRECTORY_HPP)
+#define UTILS_FS_DIRECTORY_HPP
+
+#include "utils/fs/directory_fwd.hpp"
+
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace fs {
+
+
+/// Representation of a single directory entry.
+struct directory_entry {
+ /// Name of the directory entry.
+ std::string name;
+
+ explicit directory_entry(const std::string&);
+
+ bool operator==(const directory_entry&) const;
+ bool operator!=(const directory_entry&) const;
+ bool operator<(const directory_entry&) const;
+};
+
+
+std::ostream& operator<<(std::ostream&, const directory_entry&);
+
+
+namespace detail {
+
+
+/// Forward directory iterator.
+class directory_iterator {
+ struct impl;
+
+ /// Internal implementation details.
+ std::shared_ptr< impl > _pimpl;
+
+ directory_iterator(std::shared_ptr< impl >);
+
+ friend class fs::directory;
+ static directory_iterator new_begin(const path&);
+ static directory_iterator new_end(void);
+
+public:
+ ~directory_iterator();
+
+ bool operator==(const directory_iterator&) const;
+ bool operator!=(const directory_iterator&) const;
+ directory_iterator& operator++(void);
+
+ const directory_entry& operator*(void) const;
+ const directory_entry* operator->(void) const;
+};
+
+
+} // namespace detail
+
+
+/// Representation of a local filesystem directory.
+///
+/// This class is pretty much stateless. All the directory manipulation
+/// operations happen within the iterator.
+class directory {
+public:
+ /// Public type for a constant forward directory iterator.
+ typedef detail::directory_iterator const_iterator;
+
+private:
+ struct impl;
+
+ /// Internal implementation details.
+ std::shared_ptr< impl > _pimpl;
+
+public:
+ explicit directory(const path&);
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+};
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_DIRECTORY_HPP)
diff --git a/utils/fs/directory_fwd.hpp b/utils/fs/directory_fwd.hpp
new file mode 100644
index 000000000000..50886551ca88
--- /dev/null
+++ b/utils/fs/directory_fwd.hpp
@@ -0,0 +1,55 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/directory_fwd.hpp
+/// Forward declarations for utils/fs/directory.hpp
+
+#if !defined(UTILS_FS_DIRECTORY_FWD_HPP)
+#define UTILS_FS_DIRECTORY_FWD_HPP
+
+namespace utils {
+namespace fs {
+
+
+namespace detail {
+
+
+class directory_iterator;
+
+
+} // namespace detail
+
+
+struct directory_entry;
+class directory;
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_DIRECTORY_FWD_HPP)
diff --git a/utils/fs/directory_test.cpp b/utils/fs/directory_test.cpp
new file mode 100644
index 000000000000..4c1aa2d010f4
--- /dev/null
+++ b/utils/fs/directory_test.cpp
@@ -0,0 +1,190 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/directory.hpp"
+
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/containers.ipp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+
+namespace fs = utils::fs;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(directory_entry__public_fields);
+ATF_TEST_CASE_BODY(directory_entry__public_fields)
+{
+ const fs::directory_entry entry("name");
+ ATF_REQUIRE_EQ("name", entry.name);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(directory_entry__equality);
+ATF_TEST_CASE_BODY(directory_entry__equality)
+{
+ const fs::directory_entry entry1("name");
+ const fs::directory_entry entry2("other-name");
+
+ ATF_REQUIRE( entry1 == entry1);
+ ATF_REQUIRE(!(entry1 != entry1));
+
+ ATF_REQUIRE(!(entry1 == entry2));
+ ATF_REQUIRE( entry1 != entry2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(directory_entry__sorting);
+ATF_TEST_CASE_BODY(directory_entry__sorting)
+{
+ const fs::directory_entry entry1("name");
+ const fs::directory_entry entry2("other-name");
+
+ ATF_REQUIRE(!(entry1 < entry1));
+ ATF_REQUIRE(!(entry2 < entry2));
+ ATF_REQUIRE( entry1 < entry2);
+ ATF_REQUIRE(!(entry2 < entry1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(directory_entry__format);
+ATF_TEST_CASE_BODY(directory_entry__format)
+{
+ const fs::directory_entry entry("this is the name");
+ std::ostringstream output;
+ output << entry;
+ ATF_REQUIRE_EQ("directory_entry{name='this is the name'}", output.str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__empty);
+ATF_TEST_CASE_BODY(integration__empty)
+{
+ fs::mkdir(fs::path("empty"), 0755);
+
+ std::set< fs::directory_entry > contents;
+ const fs::directory dir(fs::path("empty"));
+ for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
+ ++iter) {
+ contents.insert(*iter);
+ // While we are here, make sure both * and -> represent the same.
+ ATF_REQUIRE((*iter).name == iter->name);
+ }
+
+ std::set< fs::directory_entry > exp_contents;
+ exp_contents.insert(fs::directory_entry("."));
+ exp_contents.insert(fs::directory_entry(".."));
+
+ ATF_REQUIRE_EQ(exp_contents, contents);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__some_contents);
+ATF_TEST_CASE_BODY(integration__some_contents)
+{
+ fs::mkdir(fs::path("full"), 0755);
+ atf::utils::create_file("full/a file", "");
+ atf::utils::create_file("full/something-else", "");
+ atf::utils::create_file("full/.hidden", "");
+ fs::mkdir(fs::path("full/subdir"), 0755);
+ atf::utils::create_file("full/subdir/not-listed", "");
+
+ std::set< fs::directory_entry > contents;
+ const fs::directory dir(fs::path("full"));
+ for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
+ ++iter) {
+ contents.insert(*iter);
+ // While we are here, make sure both * and -> represent the same.
+ ATF_REQUIRE((*iter).name == iter->name);
+ }
+
+ std::set< fs::directory_entry > exp_contents;
+ exp_contents.insert(fs::directory_entry("."));
+ exp_contents.insert(fs::directory_entry(".."));
+ exp_contents.insert(fs::directory_entry(".hidden"));
+ exp_contents.insert(fs::directory_entry("a file"));
+ exp_contents.insert(fs::directory_entry("something-else"));
+ exp_contents.insert(fs::directory_entry("subdir"));
+
+ ATF_REQUIRE_EQ(exp_contents, contents);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__open_failure);
+ATF_TEST_CASE_BODY(integration__open_failure)
+{
+ const fs::directory directory(fs::path("non-existent"));
+ ATF_REQUIRE_THROW_RE(fs::system_error, "opendir(.*non-existent.*) failed",
+ directory.begin());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__iterators_equality);
+ATF_TEST_CASE_BODY(integration__iterators_equality)
+{
+ const fs::directory directory(fs::path("."));
+
+ fs::directory::const_iterator iter_ok1 = directory.begin();
+ fs::directory::const_iterator iter_ok2 = directory.begin();
+ fs::directory::const_iterator iter_end = directory.end();
+
+ ATF_REQUIRE( iter_ok1 == iter_ok1);
+ ATF_REQUIRE(!(iter_ok1 != iter_ok1));
+
+ ATF_REQUIRE( iter_ok2 == iter_ok2);
+ ATF_REQUIRE(!(iter_ok2 != iter_ok2));
+
+ ATF_REQUIRE(!(iter_ok1 == iter_ok2));
+ ATF_REQUIRE( iter_ok1 != iter_ok2);
+
+ ATF_REQUIRE(!(iter_ok1 == iter_end));
+ ATF_REQUIRE( iter_ok1 != iter_end);
+
+ ATF_REQUIRE(!(iter_ok2 == iter_end));
+ ATF_REQUIRE( iter_ok2 != iter_end);
+
+ ATF_REQUIRE( iter_end == iter_end);
+ ATF_REQUIRE(!(iter_end != iter_end));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, directory_entry__public_fields);
+ ATF_ADD_TEST_CASE(tcs, directory_entry__equality);
+ ATF_ADD_TEST_CASE(tcs, directory_entry__sorting);
+ ATF_ADD_TEST_CASE(tcs, directory_entry__format);
+
+ ATF_ADD_TEST_CASE(tcs, integration__empty);
+ ATF_ADD_TEST_CASE(tcs, integration__some_contents);
+ ATF_ADD_TEST_CASE(tcs, integration__open_failure);
+ ATF_ADD_TEST_CASE(tcs, integration__iterators_equality);
+}
diff --git a/utils/fs/exceptions.cpp b/utils/fs/exceptions.cpp
new file mode 100644
index 000000000000..102e9069ee3c
--- /dev/null
+++ b/utils/fs/exceptions.cpp
@@ -0,0 +1,162 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/exceptions.hpp"
+
+#include <cstring>
+
+#include "utils/format/macros.hpp"
+
+namespace fs = utils::fs;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+fs::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+fs::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new invalid_path_error.
+///
+/// \param textual_path Textual representation of the invalid path.
+/// \param reason Description of the error in the path.
+fs::invalid_path_error::invalid_path_error(const std::string& textual_path,
+ const std::string& reason) :
+ error(F("Invalid path '%s': %s") % textual_path % reason),
+ _textual_path(textual_path)
+{
+}
+
+
+/// Destructor for the error.
+fs::invalid_path_error::~invalid_path_error(void) throw()
+{
+}
+
+
+/// Returns the invalid path related to the exception.
+///
+/// \return The textual representation of the invalid path.
+const std::string&
+fs::invalid_path_error::invalid_path(void) const
+{
+ return _textual_path;
+}
+
+
+/// Constructs a new join_error.
+///
+/// \param textual_path1_ Textual representation of the first path.
+/// \param textual_path2_ Textual representation of the second path.
+/// \param reason Description of the error in the join operation.
+fs::join_error::join_error(const std::string& textual_path1_,
+ const std::string& textual_path2_,
+ const std::string& reason) :
+ error(F("Cannot join paths '%s' and '%s': %s") % textual_path1_ %
+ textual_path2_ % reason),
+ _textual_path1(textual_path1_),
+ _textual_path2(textual_path2_)
+{
+}
+
+
+/// Destructor for the error.
+fs::join_error::~join_error(void) throw()
+{
+}
+
+
+/// Gets the first path that caused the error in a join operation.
+///
+/// \return The textual representation of the path.
+const std::string&
+fs::join_error::textual_path1(void) const
+{
+ return _textual_path1;
+}
+
+
+/// Gets the second path that caused the error in a join operation.
+///
+/// \return The textual representation of the path.
+const std::string&
+fs::join_error::textual_path2(void) const
+{
+ return _textual_path2;
+}
+
+
+/// Constructs a new error based on an errno code.
+///
+/// \param message_ The message describing what caused the error.
+/// \param errno_ The error code.
+fs::system_error::system_error(const std::string& message_, const int errno_) :
+ error(F("%s: %s") % message_ % std::strerror(errno_)),
+ _original_errno(errno_)
+{
+}
+
+
+/// Destructor for the error.
+fs::system_error::~system_error(void) throw()
+{
+}
+
+
+
+/// \return The original errno code.
+int
+fs::system_error::original_errno(void) const throw()
+{
+ return _original_errno;
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+fs::unsupported_operation_error::unsupported_operation_error(
+ const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+fs::unsupported_operation_error::~unsupported_operation_error(void) throw()
+{
+}
diff --git a/utils/fs/exceptions.hpp b/utils/fs/exceptions.hpp
new file mode 100644
index 000000000000..32b4af2ce463
--- /dev/null
+++ b/utils/fs/exceptions.hpp
@@ -0,0 +1,110 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/exceptions.hpp
+/// Exception types raised by the fs module.
+
+#if !defined(UTILS_FS_EXCEPTIONS_HPP)
+#define UTILS_FS_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace utils {
+namespace fs {
+
+
+/// Base exception for fs errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ virtual ~error(void) throw();
+};
+
+
+/// Error denoting an invalid path while constructing a fs::path object.
+class invalid_path_error : public error {
+ /// Raw value of the invalid path.
+ std::string _textual_path;
+
+public:
+ explicit invalid_path_error(const std::string&, const std::string&);
+ virtual ~invalid_path_error(void) throw();
+
+ const std::string& invalid_path(void) const;
+};
+
+
+/// Paths cannot be joined.
+class join_error : public error {
+ /// Raw value of the first path in the join operation.
+ std::string _textual_path1;
+
+ /// Raw value of the second path in the join operation.
+ std::string _textual_path2;
+
+public:
+ explicit join_error(const std::string&, const std::string&,
+ const std::string&);
+ virtual ~join_error(void) throw();
+
+ const std::string& textual_path1(void) const;
+ const std::string& textual_path2(void) const;
+};
+
+
+/// Exceptions for errno-based errors.
+///
+/// TODO(jmmv): This code is duplicated in, at least, utils::process. Figure
+/// out a way to reuse this exception while maintaining the correct inheritance
+/// (i.e. be able to keep it as a child of fs::error).
+class system_error : public error {
+ /// Error number describing this libc error condition.
+ int _original_errno;
+
+public:
+ explicit system_error(const std::string&, const int);
+ ~system_error(void) throw();
+
+ int original_errno(void) const throw();
+};
+
+
+/// Exception to denote an unsupported operation.
+class unsupported_operation_error : public error {
+public:
+ explicit unsupported_operation_error(const std::string&);
+ virtual ~unsupported_operation_error(void) throw();
+};
+
+
+} // namespace fs
+} // namespace utils
+
+
+#endif // !defined(UTILS_FS_EXCEPTIONS_HPP)
diff --git a/utils/fs/exceptions_test.cpp b/utils/fs/exceptions_test.cpp
new file mode 100644
index 000000000000..e67a846506cc
--- /dev/null
+++ b/utils/fs/exceptions_test.cpp
@@ -0,0 +1,95 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/exceptions.hpp"
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+
+namespace fs = utils::fs;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const fs::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_path_error);
+ATF_TEST_CASE_BODY(invalid_path_error)
+{
+ const fs::invalid_path_error e("some/invalid/path", "The reason");
+ ATF_REQUIRE(std::strcmp("Invalid path 'some/invalid/path': The reason",
+ e.what()) == 0);
+ ATF_REQUIRE_EQ("some/invalid/path", e.invalid_path());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join_error);
+ATF_TEST_CASE_BODY(join_error)
+{
+ const fs::join_error e("dir1/file1", "/dir2/file2", "The reason");
+ ATF_REQUIRE(std::strcmp("Cannot join paths 'dir1/file1' and '/dir2/file2': "
+ "The reason", e.what()) == 0);
+ ATF_REQUIRE_EQ("dir1/file1", e.textual_path1());
+ ATF_REQUIRE_EQ("/dir2/file2", e.textual_path2());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(system_error);
+ATF_TEST_CASE_BODY(system_error)
+{
+ const fs::system_error e("Call failed", ENOENT);
+ const std::string expected = F("Call failed: %s") % std::strerror(ENOENT);
+ ATF_REQUIRE_EQ(expected, e.what());
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unsupported_operation_error);
+ATF_TEST_CASE_BODY(unsupported_operation_error)
+{
+ const fs::unsupported_operation_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, invalid_path_error);
+ ATF_ADD_TEST_CASE(tcs, join_error);
+ ATF_ADD_TEST_CASE(tcs, system_error);
+ ATF_ADD_TEST_CASE(tcs, unsupported_operation_error);
+}
diff --git a/utils/fs/lua_module.cpp b/utils/fs/lua_module.cpp
new file mode 100644
index 000000000000..dec410927e1a
--- /dev/null
+++ b/utils/fs/lua_module.cpp
@@ -0,0 +1,340 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/lua_module.hpp"
+
+extern "C" {
+#include <dirent.h>
+}
+
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+#include <string>
+
+#include <lutok/operations.hpp>
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Given a path, qualifies it with the module's start directory if necessary.
+///
+/// \param state The Lua state.
+/// \param path The path to qualify.
+///
+/// \return The original path if it was absolute; otherwise the original path
+/// appended to the module's start directory.
+///
+/// \throw std::runtime_error If the module's state has been corrupted.
+static fs::path
+qualify_path(lutok::state& state, const fs::path& path)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ if (path.is_absolute()) {
+ return path;
+ } else {
+ state.get_global("_fs_start_dir");
+ if (!state.is_string(-1))
+ throw std::runtime_error("Missing _fs_start_dir global variable; "
+ "state corrupted?");
+ return fs::path(state.to_string(-1)) / path;
+ }
+}
+
+
+/// Safely gets a path from the Lua state.
+///
+/// \param state The Lua state.
+/// \param index The position in the Lua stack that contains the path to query.
+///
+/// \return The queried path.
+///
+/// \throw fs::error If the value is not a valid path.
+/// \throw std::runtime_error If the value on the Lua stack is not convertible
+/// to a path.
+static fs::path
+to_path(lutok::state& state, const int index)
+{
+ if (!state.is_string(index))
+ throw std::runtime_error("Need a string parameter");
+ return fs::path(state.to_string(index));
+}
+
+
+/// Lua binding for fs::path::basename.
+///
+/// \pre stack(-1) The input path.
+/// \post stack(-1) The basename of the input path.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_basename(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path = to_path(state, -1);
+ state.push_string(path.leaf_name().c_str());
+ cleaner.forget();
+ return 1;
+}
+
+
+/// Lua binding for fs::path::dirname.
+///
+/// \pre stack(-1) The input path.
+/// \post stack(-1) The directory part of the input path.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_dirname(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path = to_path(state, -1);
+ state.push_string(path.branch_path().c_str());
+ cleaner.forget();
+ return 1;
+}
+
+
+/// Lua binding for fs::path::exists.
+///
+/// \pre stack(-1) The input path.
+/// \post stack(-1) Whether the input path exists or not.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_exists(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path = qualify_path(state, to_path(state, -1));
+ state.push_boolean(fs::exists(path));
+ cleaner.forget();
+ return 1;
+}
+
+
+/// Lua binding for the files iterator.
+///
+/// This function takes an open directory from the closure of the iterator and
+/// returns the next entry. See lua_fs_files() for the iterator generator
+/// function.
+///
+/// \pre upvalue(1) The userdata containing an open DIR* object.
+///
+/// \param state The lua state.
+///
+/// \return The number of result values, i.e. 0 if there are no more entries or
+/// 1 if an entry has been read.
+static int
+files_iterator(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ DIR** dirp = state.to_userdata< DIR* >(state.upvalue_index(1));
+ const struct dirent* entry = ::readdir(*dirp);
+ if (entry == NULL)
+ return 0;
+ else {
+ state.push_string(entry->d_name);
+ cleaner.forget();
+ return 1;
+ }
+}
+
+
+/// Lua binding for the destruction of the files iterator.
+///
+/// This function takes an open directory and closes it. See lua_fs_files() for
+/// the iterator generator function.
+///
+/// \pre stack(-1) The userdata containing an open DIR* object.
+/// \post The DIR* object is closed.
+///
+/// \param state The lua state.
+///
+/// \return The number of result values, i.e. 0.
+static int
+files_gc(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ PRE(state.is_userdata(-1));
+
+ DIR** dirp = state.to_userdata< DIR* >(-1);
+ // For some reason, this may be called more than once. I don't know why
+ // this happens, but we must protect against it.
+ if (*dirp != NULL) {
+ ::closedir(*dirp);
+ *dirp = NULL;
+ }
+
+ return 0;
+}
+
+
+/// Lua binding to create an iterator to scan the contents of a directory.
+///
+/// \pre stack(-1) The input path.
+/// \post stack(-1) The iterator function.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_files(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path = qualify_path(state, to_path(state, -1));
+
+ DIR** dirp = state.new_userdata< DIR* >();
+
+ state.new_table();
+ state.push_string("__gc");
+ state.push_cxx_function(files_gc);
+ state.set_table(-3);
+
+ state.set_metatable(-2);
+
+ *dirp = ::opendir(path.c_str());
+ if (*dirp == NULL) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to open directory: %s") %
+ std::strerror(original_errno));
+ }
+
+ state.push_cxx_closure(files_iterator, 1);
+
+ cleaner.forget();
+ return 1;
+}
+
+
+/// Lua binding for fs::path::is_absolute.
+///
+/// \pre stack(-1) The input path.
+/// \post stack(-1) Whether the input path is absolute or not.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_is_absolute(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path = to_path(state, -1);
+
+ state.push_boolean(path.is_absolute());
+ cleaner.forget();
+ return 1;
+}
+
+
+/// Lua binding for fs::path::operator/.
+///
+/// \pre stack(-2) The first input path.
+/// \pre stack(-1) The second input path.
+/// \post stack(-1) The concatenation of the two paths.
+///
+/// \param state The Lua state.
+///
+/// \return The number of result values, i.e. 1.
+static int
+lua_fs_join(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ const fs::path path1 = to_path(state, -2);
+ const fs::path path2 = to_path(state, -1);
+ state.push_string((path1 / path2).c_str());
+ cleaner.forget();
+ return 1;
+}
+
+
+} // anonymous namespace
+
+
+/// Creates a Lua 'fs' module with a default start directory of ".".
+///
+/// \post The global 'fs' symbol is set to a table that contains functions to a
+/// variety of utilites from the fs C++ module.
+///
+/// \param s The Lua state.
+void
+fs::open_fs(lutok::state& s)
+{
+ open_fs(s, fs::current_path());
+}
+
+
+/// Creates a Lua 'fs' module with an explicit start directory.
+///
+/// \post The global 'fs' symbol is set to a table that contains functions to a
+/// variety of utilites from the fs C++ module.
+///
+/// \param s The Lua state.
+/// \param start_dir The start directory to use in all operations that reference
+/// the underlying file sytem.
+void
+fs::open_fs(lutok::state& s, const fs::path& start_dir)
+{
+ lutok::stack_cleaner cleaner(s);
+
+ s.push_string(start_dir.str());
+ s.set_global("_fs_start_dir");
+
+ std::map< std::string, lutok::cxx_function > members;
+ members["basename"] = lua_fs_basename;
+ members["dirname"] = lua_fs_dirname;
+ members["exists"] = lua_fs_exists;
+ members["files"] = lua_fs_files;
+ members["is_absolute"] = lua_fs_is_absolute;
+ members["join"] = lua_fs_join;
+ lutok::create_module(s, "fs", members);
+}
diff --git a/utils/fs/lua_module.hpp b/utils/fs/lua_module.hpp
new file mode 100644
index 000000000000..c9c303b15eb7
--- /dev/null
+++ b/utils/fs/lua_module.hpp
@@ -0,0 +1,54 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/lua_module.hpp
+/// Lua bindings for the utils::fs module.
+///
+/// When the fs module is bound to Lua, the module has the concept of a "start
+/// directory". The start directory is the directory used to qualify all
+/// relative paths, and is provided at module binding time.
+
+#if !defined(UTILS_FS_LUA_MODULE_HPP)
+#define UTILS_FS_LUA_MODULE_HPP
+
+#include <lutok/state.hpp>
+
+#include "utils/fs/path.hpp"
+
+namespace utils {
+namespace fs {
+
+
+void open_fs(lutok::state&);
+void open_fs(lutok::state&, const fs::path&);
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_LUA_MODULE_HPP)
diff --git a/utils/fs/lua_module_test.cpp b/utils/fs/lua_module_test.cpp
new file mode 100644
index 000000000000..263632ded13f
--- /dev/null
+++ b/utils/fs/lua_module_test.cpp
@@ -0,0 +1,376 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/lua_module.hpp"
+
+#include <atf-c++.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/state.hpp>
+#include <lutok/test_utils.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+
+namespace fs = utils::fs;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open_fs);
+ATF_TEST_CASE_BODY(open_fs)
+{
+ lutok::state state;
+ stack_balance_checker checker(state);
+ fs::open_fs(state);
+ lutok::do_string(state, "return fs.basename", 0, 1, 0);
+ ATF_REQUIRE(state.is_function(-1));
+ lutok::do_string(state, "return fs.dirname", 0, 1, 0);
+ ATF_REQUIRE(state.is_function(-1));
+ lutok::do_string(state, "return fs.join", 0, 1, 0);
+ ATF_REQUIRE(state.is_function(-1));
+ state.pop(3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(basename__ok);
+ATF_TEST_CASE_BODY(basename__ok)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ lutok::do_string(state, "return fs.basename('/my/test//file_foobar')",
+ 0, 1, 0);
+ ATF_REQUIRE_EQ("file_foobar", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(basename__fail);
+ATF_TEST_CASE_BODY(basename__fail)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.basename({})",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.basename('')",
+ 0, 1, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dirname__ok);
+ATF_TEST_CASE_BODY(dirname__ok)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ lutok::do_string(state, "return fs.dirname('/my/test//file_foobar')",
+ 0, 1, 0);
+ ATF_REQUIRE_EQ("/my/test", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dirname__fail);
+ATF_TEST_CASE_BODY(dirname__fail)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.dirname({})",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.dirname('')",
+ 0, 1, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exists__ok);
+ATF_TEST_CASE_BODY(exists__ok)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ atf::utils::create_file("foo", "");
+
+ lutok::do_string(state, "return fs.exists('foo')", 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state, "return fs.exists('bar')", 0, 1, 0);
+ ATF_REQUIRE(!state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state,
+ F("return fs.exists('%s')") % fs::current_path(), 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exists__fail);
+ATF_TEST_CASE_BODY(exists__fail)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.exists({})",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.exists('')",
+ 0, 1, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exists__custom_start_dir);
+ATF_TEST_CASE_BODY(exists__custom_start_dir)
+{
+ lutok::state state;
+ fs::open_fs(state, fs::path("subdir"));
+
+ fs::mkdir(fs::path("subdir"), 0755);
+ atf::utils::create_file("subdir/foo", "");
+ atf::utils::create_file("bar", "");
+
+ lutok::do_string(state, "return fs.exists('foo')", 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state, "return fs.exists('subdir/foo')", 0, 1, 0);
+ ATF_REQUIRE(!state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state, "return fs.exists('bar')", 0, 1, 0);
+ ATF_REQUIRE(!state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state, "return fs.exists('../bar')", 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+
+ lutok::do_string(state,
+ F("return fs.exists('%s')") % (fs::current_path() / "bar"),
+ 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(files__none);
+ATF_TEST_CASE_BODY(files__none)
+{
+ lutok::state state;
+ state.open_table();
+ fs::open_fs(state);
+
+ fs::mkdir(fs::path("root"), 0755);
+
+ lutok::do_string(state,
+ "names = {}\n"
+ "for file in fs.files('root') do\n"
+ " table.insert(names, file)\n"
+ "end\n"
+ "table.sort(names)\n"
+ "return table.concat(names, ' ')",
+ 0, 1, 0);
+ ATF_REQUIRE_EQ(". ..", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(files__some);
+ATF_TEST_CASE_BODY(files__some)
+{
+ lutok::state state;
+ state.open_table();
+ fs::open_fs(state);
+
+ fs::mkdir(fs::path("root"), 0755);
+ atf::utils::create_file("root/file1", "");
+ atf::utils::create_file("root/file2", "");
+
+ lutok::do_string(state,
+ "names = {}\n"
+ "for file in fs.files('root') do\n"
+ " table.insert(names, file)\n"
+ "end\n"
+ "table.sort(names)\n"
+ "return table.concat(names, ' ')",
+ 0, 1, 0);
+ ATF_REQUIRE_EQ(". .. file1 file2", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(files__some_with_custom_start_dir);
+ATF_TEST_CASE_BODY(files__some_with_custom_start_dir)
+{
+ lutok::state state;
+ state.open_table();
+ fs::open_fs(state, fs::current_path() / "root");
+
+ fs::mkdir(fs::path("root"), 0755);
+ atf::utils::create_file("root/file1", "");
+ atf::utils::create_file("root/file2", "");
+ atf::utils::create_file("file3", "");
+
+ lutok::do_string(state,
+ "names = {}\n"
+ "for file in fs.files('.') do\n"
+ " table.insert(names, file)\n"
+ "end\n"
+ "table.sort(names)\n"
+ "return table.concat(names, ' ')",
+ 0, 1, 0);
+ ATF_REQUIRE_EQ(". .. file1 file2", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(files__fail_arg);
+ATF_TEST_CASE_BODY(files__fail_arg)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string parameter",
+ lutok::do_string(state, "fs.files({})", 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "fs.files('')", 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(files__fail_opendir);
+ATF_TEST_CASE_BODY(files__fail_opendir)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Failed to open directory",
+ lutok::do_string(state, "fs.files('root')", 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_absolute__ok);
+ATF_TEST_CASE_BODY(is_absolute__ok)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ lutok::do_string(state, "return fs.is_absolute('my/test//file_foobar')",
+ 0, 1, 0);
+ ATF_REQUIRE(!state.to_boolean(-1));
+ lutok::do_string(state, "return fs.is_absolute('/my/test//file_foobar')",
+ 0, 1, 0);
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_absolute__fail);
+ATF_TEST_CASE_BODY(is_absolute__fail)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.is_absolute({})",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.is_absolute('')",
+ 0, 1, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__ok);
+ATF_TEST_CASE_BODY(join__ok)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ lutok::do_string(state, "return fs.join('/a/b///', 'c/d')", 0, 1, 0);
+ ATF_REQUIRE_EQ("/a/b/c/d", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__fail);
+ATF_TEST_CASE_BODY(join__fail)
+{
+ lutok::state state;
+ fs::open_fs(state);
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.join({}, 'a')",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Need a string",
+ lutok::do_string(state, "return fs.join('a', {})",
+ 0, 1, 0));
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.join('', 'a')",
+ 0, 1, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid path",
+ lutok::do_string(state, "return fs.join('a', '')",
+ 0, 1, 0));
+
+ ATF_REQUIRE_THROW_RE(lutok::error, "Cannot join.*'a/b'.*'/c'",
+ lutok::do_string(state, "fs.join('a/b', '/c')",
+ 0, 0, 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, open_fs);
+
+ ATF_ADD_TEST_CASE(tcs, basename__ok);
+ ATF_ADD_TEST_CASE(tcs, basename__fail);
+
+ ATF_ADD_TEST_CASE(tcs, dirname__ok);
+ ATF_ADD_TEST_CASE(tcs, dirname__fail);
+
+ ATF_ADD_TEST_CASE(tcs, exists__ok);
+ ATF_ADD_TEST_CASE(tcs, exists__fail);
+ ATF_ADD_TEST_CASE(tcs, exists__custom_start_dir);
+
+ ATF_ADD_TEST_CASE(tcs, files__none);
+ ATF_ADD_TEST_CASE(tcs, files__some);
+ ATF_ADD_TEST_CASE(tcs, files__some_with_custom_start_dir);
+ ATF_ADD_TEST_CASE(tcs, files__fail_arg);
+ ATF_ADD_TEST_CASE(tcs, files__fail_opendir);
+
+ ATF_ADD_TEST_CASE(tcs, is_absolute__ok);
+ ATF_ADD_TEST_CASE(tcs, is_absolute__fail);
+
+ ATF_ADD_TEST_CASE(tcs, join__ok);
+ ATF_ADD_TEST_CASE(tcs, join__fail);
+}
diff --git a/utils/fs/operations.cpp b/utils/fs/operations.cpp
new file mode 100644
index 000000000000..7a96d0b2058a
--- /dev/null
+++ b/utils/fs/operations.cpp
@@ -0,0 +1,803 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/operations.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#if defined(HAVE_SYS_MOUNT_H)
+# include <sys/mount.h>
+#endif
+#include <sys/stat.h>
+#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
+# include <sys/statvfs.h>
+#endif
+#if defined(HAVE_SYS_VFS_H)
+# include <sys/vfs.h>
+#endif
+#include <sys/wait.h>
+
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "utils/auto_array.ipp"
+#include "utils/defs.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/directory.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/units.hpp"
+
+namespace fs = utils::fs;
+namespace units = utils::units;
+
+using utils::optional;
+
+
+namespace {
+
+
+/// Operating systems recognized by the code below.
+enum os_type {
+ os_unsupported = 0,
+ os_freebsd,
+ os_linux,
+ os_netbsd,
+ os_sunos,
+};
+
+
+/// The current operating system.
+static enum os_type current_os =
+#if defined(__FreeBSD__)
+ os_freebsd
+#elif defined(__linux__)
+ os_linux
+#elif defined(__NetBSD__)
+ os_netbsd
+#elif defined(__SunOS__)
+ os_sunos
+#else
+ os_unsupported
+#endif
+ ;
+
+
+/// Specifies if a real unmount(2) is available.
+///
+/// We use this as a constant instead of a macro so that we can compile both
+/// versions of the unmount code unconditionally. This is a way to prevent
+/// compilation bugs going unnoticed for long.
+static const bool have_unmount2 =
+#if defined(HAVE_UNMOUNT)
+ true;
+#else
+ false;
+#endif
+
+
+#if !defined(UMOUNT)
+/// Fake replacement value to the path to umount(8).
+# define UMOUNT "do-not-use-this-value"
+#else
+# if defined(HAVE_UNMOUNT)
+# error "umount(8) detected when unmount(2) is also available"
+# endif
+#endif
+
+
+#if !defined(HAVE_UNMOUNT)
+/// Fake unmount(2) function for systems without it.
+///
+/// This is only provided to allow our code to compile in all platforms
+/// regardless of whether they actually have an unmount(2) or not.
+///
+/// \return -1 to indicate error, although this should never happen.
+static int
+unmount(const char* /* path */,
+ const int /* flags */)
+{
+ PRE(false);
+ return -1;
+}
+#endif
+
+
+/// Error code returned by subprocess to indicate a controlled failure.
+const int exit_known_error = 123;
+
+
+static void run_mount_tmpfs(const fs::path&, const uint64_t) UTILS_NORETURN;
+
+
+/// Executes 'mount -t tmpfs' (or a similar variant).
+///
+/// This function must be called from a subprocess as it never returns.
+///
+/// \param mount_point Location on which to mount a tmpfs.
+/// \param size The size of the tmpfs to mount. If 0, use unlimited.
+static void
+run_mount_tmpfs(const fs::path& mount_point, const uint64_t size)
+{
+ const char* mount_args[16];
+ std::string size_arg;
+
+ std::size_t last = 0;
+ switch (current_os) {
+ case os_freebsd:
+ mount_args[last++] = "mount";
+ mount_args[last++] = "-ttmpfs";
+ if (size > 0) {
+ size_arg = F("-osize=%s") % size;
+ mount_args[last++] = size_arg.c_str();
+ }
+ mount_args[last++] = "tmpfs";
+ mount_args[last++] = mount_point.c_str();
+ break;
+
+ case os_linux:
+ mount_args[last++] = "mount";
+ mount_args[last++] = "-ttmpfs";
+ if (size > 0) {
+ size_arg = F("-osize=%s") % size;
+ mount_args[last++] = size_arg.c_str();
+ }
+ mount_args[last++] = "tmpfs";
+ mount_args[last++] = mount_point.c_str();
+ break;
+
+ case os_netbsd:
+ mount_args[last++] = "mount";
+ mount_args[last++] = "-ttmpfs";
+ if (size > 0) {
+ size_arg = F("-o-s%s") % size;
+ mount_args[last++] = size_arg.c_str();
+ }
+ mount_args[last++] = "tmpfs";
+ mount_args[last++] = mount_point.c_str();
+ break;
+
+ case os_sunos:
+ mount_args[last++] = "mount";
+ mount_args[last++] = "-Ftmpfs";
+ if (size > 0) {
+ size_arg = F("-o-s%s") % size;
+ mount_args[last++] = size_arg.c_str();
+ }
+ mount_args[last++] = "tmpfs";
+ mount_args[last++] = mount_point.c_str();
+ break;
+
+ default:
+ std::cerr << "Don't know how to mount a temporary file system in this "
+ "host operating system\n";
+ std::exit(exit_known_error);
+ }
+ mount_args[last] = NULL;
+
+ const char** arg;
+ std::cout << "Mounting tmpfs onto " << mount_point << " with:";
+ for (arg = &mount_args[0]; *arg != NULL; arg++)
+ std::cout << " " << *arg;
+ std::cout << "\n";
+
+ const int ret = ::execvp(mount_args[0],
+ UTILS_UNCONST(char* const, mount_args));
+ INV(ret == -1);
+ std::cerr << "Failed to exec " << mount_args[0] << "\n";
+ std::exit(EXIT_FAILURE);
+}
+
+
+/// Unmounts a file system using unmount(2).
+///
+/// \pre unmount(2) must be available; i.e. have_unmount2 must be true.
+///
+/// \param mount_point The file system to unmount.
+///
+/// \throw fs::system_error If the call to unmount(2) fails.
+static void
+unmount_with_unmount2(const fs::path& mount_point)
+{
+ PRE(have_unmount2);
+
+ if (::unmount(mount_point.c_str(), 0) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("unmount(%s) failed") % mount_point,
+ original_errno);
+ }
+}
+
+
+/// Unmounts a file system using umount(8).
+///
+/// \pre umount(2) must not be available; i.e. have_unmount2 must be false.
+///
+/// \param mount_point The file system to unmount.
+///
+/// \throw fs::error If the execution of umount(8) fails.
+static void
+unmount_with_umount8(const fs::path& mount_point)
+{
+ PRE(!have_unmount2);
+
+ const pid_t pid = ::fork();
+ if (pid == -1) {
+ const int original_errno = errno;
+ throw fs::system_error("Cannot fork to execute unmount tool",
+ original_errno);
+ } else if (pid == 0) {
+ const int ret = ::execlp(UMOUNT, "umount", mount_point.c_str(), NULL);
+ INV(ret == -1);
+ std::cerr << "Failed to exec " UMOUNT "\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ int status;
+retry:
+ if (::waitpid(pid, &status, 0) == -1) {
+ const int original_errno = errno;
+ if (errno == EINTR)
+ goto retry;
+ throw fs::system_error("Failed to wait for unmount subprocess",
+ original_errno);
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == EXIT_SUCCESS)
+ return;
+ else
+ throw fs::error(F("Failed to unmount %s; returned exit code %s")
+ % mount_point % WEXITSTATUS(status));
+ } else
+ throw fs::error(F("Failed to unmount %s; unmount tool received signal")
+ % mount_point);
+}
+
+
+/// Stats a file, without following links.
+///
+/// \param path The file to stat.
+///
+/// \return The stat structure on success.
+///
+/// \throw system_error An error on failure.
+static struct ::stat
+safe_stat(const fs::path& path)
+{
+ struct ::stat sb;
+ if (::lstat(path.c_str(), &sb) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Cannot get information about %s") % path,
+ original_errno);
+ }
+ return sb;
+}
+
+
+} // anonymous namespace
+
+
+/// Copies a file.
+///
+/// \param source The file to copy.
+/// \param target The destination of the new copy; must be a file name, not a
+/// directory.
+///
+/// \throw error If there is a problem copying the file.
+void
+fs::copy(const fs::path& source, const fs::path& target)
+{
+ std::ifstream input(source.c_str());
+ if (!input)
+ throw error(F("Cannot open copy source %s") % source);
+
+ std::ofstream output(target.c_str());
+ if (!output)
+ throw error(F("Cannot create copy target %s") % target);
+
+ char buffer[1024];
+ while (input.good()) {
+ input.read(buffer, sizeof(buffer));
+ if (input.good() || input.eof())
+ output.write(buffer, input.gcount());
+ }
+ if (!input.good() && !input.eof())
+ throw error(F("Error while reading input file %s") % source);
+}
+
+
+/// Queries the path to the current directory.
+///
+/// \return The path to the current directory.
+///
+/// \throw fs::error If there is a problem querying the current directory.
+fs::path
+fs::current_path(void)
+{
+ char* cwd;
+#if defined(HAVE_GETCWD_DYN)
+ cwd = ::getcwd(NULL, 0);
+#else
+ cwd = ::getcwd(NULL, MAXPATHLEN);
+#endif
+ if (cwd == NULL) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Failed to get current working directory"),
+ original_errno);
+ }
+
+ try {
+ const fs::path result(cwd);
+ std::free(cwd);
+ return result;
+ } catch (...) {
+ std::free(cwd);
+ throw;
+ }
+}
+
+
+/// Checks if a file exists.
+///
+/// Be aware that this is racy in the same way as access(2) is.
+///
+/// \param path The file to check the existance of.
+///
+/// \return True if the file exists; false otherwise.
+bool
+fs::exists(const fs::path& path)
+{
+ return ::access(path.c_str(), F_OK) == 0;
+}
+
+
+/// Locates a file in the PATH.
+///
+/// \param name The file to locate.
+///
+/// \return The path to the located file or none if it was not found. The
+/// returned path is always absolute.
+optional< fs::path >
+fs::find_in_path(const char* name)
+{
+ const optional< std::string > current_path = utils::getenv("PATH");
+ if (!current_path || current_path.get().empty())
+ return none;
+
+ std::istringstream path_input(current_path.get() + ":");
+ std::string path_component;
+ while (std::getline(path_input, path_component, ':').good()) {
+ const fs::path candidate = path_component.empty() ?
+ fs::path(name) : (fs::path(path_component) / name);
+ if (exists(candidate)) {
+ if (candidate.is_absolute())
+ return utils::make_optional(candidate);
+ else
+ return utils::make_optional(candidate.to_absolute());
+ }
+ }
+ return none;
+}
+
+
+/// Calculates the free space in a given file system.
+///
+/// \param path Path to a file in the file system for which to check the free
+/// disk space.
+///
+/// \return The amount of free space usable by a non-root user.
+///
+/// \throw system_error If the call to statfs(2) fails.
+utils::units::bytes
+fs::free_disk_space(const fs::path& path)
+{
+#if defined(HAVE_STATVFS)
+ struct ::statvfs buf;
+ if (::statvfs(path.c_str(), &buf) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Failed to stat file system for %s") % path,
+ original_errno);
+ }
+ return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail);
+#elif defined(HAVE_STATFS)
+ struct ::statfs buf;
+ if (::statfs(path.c_str(), &buf) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Failed to stat file system for %s") % path,
+ original_errno);
+ }
+ return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail);
+#else
+# error "Don't know how to query free disk space"
+#endif
+}
+
+
+/// Checks if the given path is a directory or not.
+///
+/// \return True if the path is a directory; false otherwise.
+bool
+fs::is_directory(const fs::path& path)
+{
+ const struct ::stat sb = safe_stat(path);
+ return S_ISDIR(sb.st_mode);
+}
+
+
+/// Creates a directory.
+///
+/// \param dir The path to the directory to create.
+/// \param mode The permissions for the new directory.
+///
+/// \throw system_error If the call to mkdir(2) fails.
+void
+fs::mkdir(const fs::path& dir, const int mode)
+{
+ if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Failed to create directory %s") % dir,
+ original_errno);
+ }
+}
+
+
+/// Creates a directory and any missing parents.
+///
+/// This is separate from the fs::mkdir function to clearly differentiate the
+/// libc wrapper from the more complex algorithm implemented here.
+///
+/// \param dir The path to the directory to create.
+/// \param mode The permissions for the new directories.
+///
+/// \throw system_error If any call to mkdir(2) fails.
+void
+fs::mkdir_p(const fs::path& dir, const int mode)
+{
+ try {
+ fs::mkdir(dir, mode);
+ } catch (const fs::system_error& e) {
+ if (e.original_errno() == ENOENT) {
+ fs::mkdir_p(dir.branch_path(), mode);
+ fs::mkdir(dir, mode);
+ } else if (e.original_errno() != EEXIST)
+ throw e;
+ }
+}
+
+
+/// Creates a temporary directory that is world readable/accessible.
+///
+/// The temporary directory is created using mkdtemp(3) using the provided
+/// template. This should be most likely used in conjunction with
+/// fs::auto_directory.
+///
+/// The temporary directory is given read and execute permissions to everyone
+/// and thus should not be used to protect data that may be subject to snooping.
+/// This goes together with the assumption that this function is used to create
+/// temporary directories for test cases, and that those test cases may
+/// sometimes be executed as an unprivileged user. In those cases, we need to
+/// support two different things:
+///
+/// - Allow the unprivileged code to write to files in the work directory by
+/// name (e.g. to write the results file, whose name is provided by the
+/// monitor code running as root). This requires us to grant search
+/// permissions.
+///
+/// - Allow the test cases themselves to call getcwd(3) at any point. At least
+/// on NetBSD 7.x, getcwd(3) requires both read and search permissions on all
+/// path components leading to the current directory. This requires us to
+/// grant both read and search permissions.
+///
+/// TODO(jmmv): A cleaner way to support this would be for the test executor to
+/// create two work directory hierarchies directly rooted under TMPDIR: one for
+/// root and one for the unprivileged user. However, that requires more
+/// bookkeeping for no real gain, because we are not really trying to protect
+/// the data within our temporary directories against attacks.
+///
+/// \param path_template The template for the temporary path, which is a
+/// basename that is created within the TMPDIR. Must contain the XXXXXX
+/// pattern, which is atomically replaced by a random unique string.
+///
+/// \return The generated path for the temporary directory.
+///
+/// \throw fs::system_error If the call to mkdtemp(3) fails.
+fs::path
+fs::mkdtemp_public(const std::string& path_template)
+{
+ PRE(path_template.find("XXXXXX") != std::string::npos);
+
+ const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
+ const fs::path full_template = tmpdir / path_template;
+
+ utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
+ std::strcpy(buf.get(), full_template.c_str());
+ if (::mkdtemp(buf.get()) == NULL) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Cannot create temporary directory using "
+ "template %s") % full_template,
+ original_errno);
+ }
+ const fs::path path(buf.get());
+
+ if (::chmod(path.c_str(), 0755) == -1) {
+ const int original_errno = errno;
+
+ try {
+ rmdir(path);
+ } catch (const fs::system_error& e) {
+ // This really should not fail. We just created the directory and
+ // have not written anything to it so there is no reason for this to
+ // fail. But better handle the failure just in case.
+ LW(F("Failed to delete just-created temporary directory %s")
+ % path);
+ }
+
+ throw fs::system_error(F("Failed to grant search permissions on "
+ "temporary directory %s") % path,
+ original_errno);
+ }
+
+ return path;
+}
+
+
+/// Creates a temporary file.
+///
+/// The temporary file is created using mkstemp(3) using the provided template.
+/// This should be most likely used in conjunction with fs::auto_file.
+///
+/// \param path_template The template for the temporary path, which is a
+/// basename that is created within the TMPDIR. Must contain the XXXXXX
+/// pattern, which is atomically replaced by a random unique string.
+///
+/// \return The generated path for the temporary directory.
+///
+/// \throw fs::system_error If the call to mkstemp(3) fails.
+fs::path
+fs::mkstemp(const std::string& path_template)
+{
+ PRE(path_template.find("XXXXXX") != std::string::npos);
+
+ const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
+ const fs::path full_template = tmpdir / path_template;
+
+ utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
+ std::strcpy(buf.get(), full_template.c_str());
+ if (::mkstemp(buf.get()) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Cannot create temporary file using template "
+ "%s") % full_template, original_errno);
+ }
+ return fs::path(buf.get());
+}
+
+
+/// Mounts a temporary file system with unlimited size.
+///
+/// \param in_mount_point The path on which the file system will be mounted.
+///
+/// \throw fs::system_error If the attempt to mount process fails.
+/// \throw fs::unsupported_operation_error If the code does not know how to
+/// mount a temporary file system in the current operating system.
+void
+fs::mount_tmpfs(const fs::path& in_mount_point)
+{
+ mount_tmpfs(in_mount_point, units::bytes());
+}
+
+
+/// Mounts a temporary file system.
+///
+/// \param in_mount_point The path on which the file system will be mounted.
+/// \param size The size of the tmpfs to mount. If 0, use unlimited.
+///
+/// \throw fs::system_error If the attempt to mount process fails.
+/// \throw fs::unsupported_operation_error If the code does not know how to
+/// mount a temporary file system in the current operating system.
+void
+fs::mount_tmpfs(const fs::path& in_mount_point, const units::bytes& size)
+{
+ // SunOS's mount(8) requires paths to be absolute. To err on the side of
+ // caution, let's make the mount point absolute in all cases.
+ const fs::path mount_point = in_mount_point.is_absolute() ?
+ in_mount_point : in_mount_point.to_absolute();
+
+ const pid_t pid = ::fork();
+ if (pid == -1) {
+ const int original_errno = errno;
+ throw fs::system_error("Cannot fork to execute mount tool",
+ original_errno);
+ }
+ if (pid == 0)
+ run_mount_tmpfs(mount_point, size);
+
+ int status;
+retry:
+ if (::waitpid(pid, &status, 0) == -1) {
+ const int original_errno = errno;
+ if (errno == EINTR)
+ goto retry;
+ throw fs::system_error("Failed to wait for mount subprocess",
+ original_errno);
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == exit_known_error)
+ throw fs::unsupported_operation_error(
+ "Don't know how to mount a tmpfs on this operating system");
+ else if (WEXITSTATUS(status) == EXIT_SUCCESS)
+ return;
+ else
+ throw fs::error(F("Failed to mount tmpfs on %s; returned exit "
+ "code %s") % mount_point % WEXITSTATUS(status));
+ } else {
+ throw fs::error(F("Failed to mount tmpfs on %s; mount tool "
+ "received signal") % mount_point);
+ }
+}
+
+
+/// Recursively removes a directory.
+///
+/// This operation simulates a "rm -r". No effort is made to forcibly delete
+/// files and no attention is paid to mount points.
+///
+/// \param directory The directory to remove.
+///
+/// \throw fs::error If there is a problem removing any directory or file.
+void
+fs::rm_r(const fs::path& directory)
+{
+ const fs::directory dir(directory);
+
+ for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
+ ++iter) {
+ if (iter->name == "." || iter->name == "..")
+ continue;
+
+ const fs::path entry = directory / iter->name;
+
+ if (fs::is_directory(entry)) {
+ LD(F("Descending into %s") % entry);
+ fs::rm_r(entry);
+ } else {
+ LD(F("Removing file %s") % entry);
+ fs::unlink(entry);
+ }
+ }
+
+ LD(F("Removing empty directory %s") % directory);
+ fs::rmdir(directory);
+}
+
+
+/// Removes an empty directory.
+///
+/// \param file The directory to remove.
+///
+/// \throw fs::system_error If the call to rmdir(2) fails.
+void
+fs::rmdir(const path& file)
+{
+ if (::rmdir(file.c_str()) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Removal of %s failed") % file,
+ original_errno);
+ }
+}
+
+
+/// Obtains all the entries in a directory.
+///
+/// \param path The directory to scan.
+///
+/// \return The set of all directory entries in the given directory.
+///
+/// \throw fs::system_error If reading the directory fails for any reason.
+std::set< fs::directory_entry >
+fs::scan_directory(const fs::path& path)
+{
+ std::set< fs::directory_entry > contents;
+
+ fs::directory dir(path);
+ for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
+ ++iter) {
+ contents.insert(*iter);
+ }
+
+ return contents;
+}
+
+
+/// Removes a file.
+///
+/// \param file The file to remove.
+///
+/// \throw fs::system_error If the call to unlink(2) fails.
+void
+fs::unlink(const path& file)
+{
+ if (::unlink(file.c_str()) == -1) {
+ const int original_errno = errno;
+ throw fs::system_error(F("Removal of %s failed") % file,
+ original_errno);
+ }
+}
+
+
+/// Unmounts a file system.
+///
+/// \param in_mount_point The file system to unmount.
+///
+/// \throw fs::error If the unmount fails.
+void
+fs::unmount(const fs::path& in_mount_point)
+{
+ // FreeBSD's unmount(2) requires paths to be absolute. To err on the side
+ // of caution, let's make it absolute in all cases.
+ const fs::path mount_point = in_mount_point.is_absolute() ?
+ in_mount_point : in_mount_point.to_absolute();
+
+ static const int unmount_retries = 3;
+ static const int unmount_retry_delay_seconds = 1;
+
+ int retries = unmount_retries;
+retry:
+ try {
+ if (have_unmount2) {
+ unmount_with_unmount2(mount_point);
+ } else {
+ unmount_with_umount8(mount_point);
+ }
+ } catch (const fs::system_error& error) {
+ if (error.original_errno() == EBUSY && retries > 0) {
+ LW(F("%s busy; unmount retries left %s") % mount_point % retries);
+ retries--;
+ ::sleep(unmount_retry_delay_seconds);
+ goto retry;
+ }
+ throw;
+ }
+}
diff --git a/utils/fs/operations.hpp b/utils/fs/operations.hpp
new file mode 100644
index 000000000000..bd7560ffc048
--- /dev/null
+++ b/utils/fs/operations.hpp
@@ -0,0 +1,72 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/operations.hpp
+/// File system algorithms and access functions.
+///
+/// The functions in this module are exception-based, type-improved wrappers
+/// over the functions provided by libc.
+
+#if !defined(UTILS_FS_OPERATIONS_HPP)
+#define UTILS_FS_OPERATIONS_HPP
+
+#include <set>
+#include <string>
+
+#include "utils/fs/directory_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+#include "utils/units_fwd.hpp"
+
+namespace utils {
+namespace fs {
+
+
+void copy(const fs::path&, const fs::path&);
+path current_path(void);
+bool exists(const fs::path&);
+utils::optional< path > find_in_path(const char*);
+utils::units::bytes free_disk_space(const fs::path&);
+bool is_directory(const fs::path&);
+void mkdir(const path&, const int);
+void mkdir_p(const path&, const int);
+fs::path mkdtemp_public(const std::string&);
+fs::path mkstemp(const std::string&);
+void mount_tmpfs(const path&);
+void mount_tmpfs(const path&, const units::bytes&);
+void rm_r(const path&);
+void rmdir(const path&);
+std::set< directory_entry > scan_directory(const path&);
+void unlink(const path&);
+void unmount(const path&);
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_OPERATIONS_HPP)
diff --git a/utils/fs/operations_test.cpp b/utils/fs/operations_test.cpp
new file mode 100644
index 000000000000..f1349351166e
--- /dev/null
+++ b/utils/fs/operations_test.cpp
@@ -0,0 +1,826 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/operations.hpp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/env.hpp"
+#include "utils/format/containers.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/directory.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/stream.hpp"
+#include "utils/units.hpp"
+
+namespace fs = utils::fs;
+namespace passwd = utils::passwd;
+namespace units = utils::units;
+
+using utils::optional;
+
+
+namespace {
+
+
+/// Checks if a directory entry exists and matches a specific type.
+///
+/// \param dir The directory in which to look for the entry.
+/// \param name The name of the entry to look up.
+/// \param expected_type The expected type of the file as given by dir(5).
+///
+/// \return True if the entry exists and matches the given type; false
+/// otherwise.
+static bool
+lookup(const char* dir, const char* name, const unsigned int expected_type)
+{
+ DIR* dirp = ::opendir(dir);
+ ATF_REQUIRE(dirp != NULL);
+
+ bool found = false;
+ struct dirent* dp;
+ while (!found && (dp = readdir(dirp)) != NULL) {
+ if (std::strcmp(dp->d_name, name) == 0) {
+ struct ::stat s;
+ const fs::path lookup_path = fs::path(dir) / name;
+ ATF_REQUIRE(::stat(lookup_path.c_str(), &s) != -1);
+ if ((s.st_mode & S_IFMT) == expected_type) {
+ found = true;
+ }
+ }
+ }
+ ::closedir(dirp);
+ return found;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(copy__ok);
+ATF_TEST_CASE_BODY(copy__ok)
+{
+ const fs::path source("f1.txt");
+ const fs::path target("f2.txt");
+
+ atf::utils::create_file(source.str(), "This is the input");
+ fs::copy(source, target);
+ ATF_REQUIRE(atf::utils::compare_file(target.str(), "This is the input"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(copy__fail_open);
+ATF_TEST_CASE_BODY(copy__fail_open)
+{
+ const fs::path source("f1.txt");
+ const fs::path target("f2.txt");
+
+ ATF_REQUIRE_THROW_RE(fs::error, "Cannot open copy source f1.txt",
+ fs::copy(source, target));
+}
+
+
+ATF_TEST_CASE(copy__fail_create);
+ATF_TEST_CASE_HEAD(copy__fail_create)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(copy__fail_create)
+{
+ const fs::path source("f1.txt");
+ const fs::path target("f2.txt");
+
+ atf::utils::create_file(target.str(), "Do not override");
+ ATF_REQUIRE(::chmod(target.c_str(), 0444) != -1);
+
+ atf::utils::create_file(source.str(), "This is the input");
+ ATF_REQUIRE_THROW_RE(fs::error, "Cannot create copy target f2.txt",
+ fs::copy(source, target));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(current_path__ok);
+ATF_TEST_CASE_BODY(current_path__ok)
+{
+ const fs::path previous = fs::current_path();
+ fs::mkdir(fs::path("root"), 0755);
+ ATF_REQUIRE(::chdir("root") != -1);
+ const fs::path cwd = fs::current_path();
+ ATF_REQUIRE_EQ(cwd.str().length() - 5, cwd.str().find("/root"));
+ ATF_REQUIRE_EQ(previous / "root", cwd);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(current_path__enoent);
+ATF_TEST_CASE_BODY(current_path__enoent)
+{
+ const fs::path previous = fs::current_path();
+ fs::mkdir(fs::path("root"), 0755);
+ ATF_REQUIRE(::chdir("root") != -1);
+ ATF_REQUIRE(::rmdir("../root") != -1);
+ try {
+ (void)fs::current_path();
+ fail("system_errpr not raised");
+ } catch (const fs::system_error& e) {
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exists);
+ATF_TEST_CASE_BODY(exists)
+{
+ const fs::path dir("dir");
+ ATF_REQUIRE(!fs::exists(dir));
+ fs::mkdir(dir, 0755);
+ ATF_REQUIRE(fs::exists(dir));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__no_path);
+ATF_TEST_CASE_BODY(find_in_path__no_path)
+{
+ utils::unsetenv("PATH");
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+ atf::utils::create_file("ls", "");
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__empty_path);
+ATF_TEST_CASE_BODY(find_in_path__empty_path)
+{
+ utils::setenv("PATH", "");
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+ atf::utils::create_file("ls", "");
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__one_component);
+ATF_TEST_CASE_BODY(find_in_path__one_component)
+{
+ const fs::path dir = fs::current_path() / "bin";
+ fs::mkdir(dir, 0755);
+ utils::setenv("PATH", dir.str());
+
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+ atf::utils::create_file((dir / "ls").str(), "");
+ ATF_REQUIRE_EQ(dir / "ls", fs::find_in_path("ls").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__many_components);
+ATF_TEST_CASE_BODY(find_in_path__many_components)
+{
+ const fs::path dir1 = fs::current_path() / "dir1";
+ const fs::path dir2 = fs::current_path() / "dir2";
+ fs::mkdir(dir1, 0755);
+ fs::mkdir(dir2, 0755);
+ utils::setenv("PATH", dir1.str() + ":" + dir2.str());
+
+ ATF_REQUIRE(!fs::find_in_path("ls"));
+ atf::utils::create_file((dir2 / "ls").str(), "");
+ ATF_REQUIRE_EQ(dir2 / "ls", fs::find_in_path("ls").get());
+ atf::utils::create_file((dir1 / "ls").str(), "");
+ ATF_REQUIRE_EQ(dir1 / "ls", fs::find_in_path("ls").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__current_directory);
+ATF_TEST_CASE_BODY(find_in_path__current_directory)
+{
+ utils::setenv("PATH", "bin:");
+
+ ATF_REQUIRE(!fs::find_in_path("foo-bar"));
+ atf::utils::create_file("foo-bar", "");
+ ATF_REQUIRE_EQ(fs::path("foo-bar").to_absolute(),
+ fs::find_in_path("foo-bar").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_in_path__always_absolute);
+ATF_TEST_CASE_BODY(find_in_path__always_absolute)
+{
+ fs::mkdir(fs::path("my-bin"), 0755);
+ utils::setenv("PATH", "my-bin");
+
+ ATF_REQUIRE(!fs::find_in_path("abcd"));
+ atf::utils::create_file("my-bin/abcd", "");
+ ATF_REQUIRE_EQ(fs::path("my-bin/abcd").to_absolute(),
+ fs::find_in_path("abcd").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(free_disk_space__ok__smoke);
+ATF_TEST_CASE_BODY(free_disk_space__ok__smoke)
+{
+ const units::bytes space = fs::free_disk_space(fs::path("."));
+ ATF_REQUIRE(space > units::MB); // Simple test that should always pass.
+}
+
+
+/// Unmounts a directory without raising errors.
+///
+/// \param cookie Name of a file that exists while the mount point is still
+/// mounted. Used to prevent a double-unmount, which would print a
+/// misleading error message.
+/// \param mount_point Path to the mount point to unmount.
+static void
+cleanup_mount_point(const fs::path& cookie, const fs::path& mount_point)
+{
+ try {
+ if (fs::exists(cookie)) {
+ fs::unmount(mount_point);
+ }
+ } catch (const std::runtime_error& e) {
+ std::cerr << "Failed trying to unmount " + mount_point.str() +
+ " during cleanup: " << e.what() << '\n';
+ }
+}
+
+
+ATF_TEST_CASE_WITH_CLEANUP(free_disk_space__ok__real);
+ATF_TEST_CASE_HEAD(free_disk_space__ok__real)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(free_disk_space__ok__real)
+{
+ try {
+ const fs::path mount_point("mount_point");
+ fs::mkdir(mount_point, 0755);
+ fs::mount_tmpfs(mount_point, units::bytes(32 * units::MB));
+ atf::utils::create_file("mounted", "");
+ const units::bytes space = fs::free_disk_space(fs::path(mount_point));
+ fs::unmount(mount_point);
+ fs::unlink(fs::path("mounted"));
+ ATF_REQUIRE(space < 35 * units::MB);
+ ATF_REQUIRE(space > 28 * units::MB);
+ } catch (const fs::unsupported_operation_error& e) {
+ ATF_SKIP(e.what());
+ }
+}
+ATF_TEST_CASE_CLEANUP(free_disk_space__ok__real)
+{
+ cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(free_disk_space__fail);
+ATF_TEST_CASE_BODY(free_disk_space__fail)
+{
+ ATF_REQUIRE_THROW_RE(fs::error, "Failed to stat file system for missing",
+ fs::free_disk_space(fs::path("missing")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_directory__ok);
+ATF_TEST_CASE_BODY(is_directory__ok)
+{
+ const fs::path file("file");
+ atf::utils::create_file(file.str(), "");
+ ATF_REQUIRE(!fs::is_directory(file));
+
+ const fs::path dir("dir");
+ fs::mkdir(dir, 0755);
+ ATF_REQUIRE(fs::is_directory(dir));
+}
+
+
+ATF_TEST_CASE_WITH_CLEANUP(is_directory__fail);
+ATF_TEST_CASE_HEAD(is_directory__fail)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(is_directory__fail)
+{
+ fs::mkdir(fs::path("dir"), 0000);
+ ATF_REQUIRE_THROW(fs::error, fs::is_directory(fs::path("dir/foo")));
+}
+ATF_TEST_CASE_CLEANUP(is_directory__fail)
+{
+ if (::chmod("dir", 0755) == -1) {
+ // If we cannot restore the original permissions, we cannot do much
+ // more. However, leaving an unwritable directory behind will cause the
+ // runtime engine to report us as broken.
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdir__ok);
+ATF_TEST_CASE_BODY(mkdir__ok)
+{
+ fs::mkdir(fs::path("dir"), 0755);
+ ATF_REQUIRE(lookup(".", "dir", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdir__enoent);
+ATF_TEST_CASE_BODY(mkdir__enoent)
+{
+ try {
+ fs::mkdir(fs::path("dir1/dir2"), 0755);
+ fail("system_error not raised");
+ } catch (const fs::system_error& e) {
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+ }
+ ATF_REQUIRE(!lookup(".", "dir1", S_IFDIR));
+ ATF_REQUIRE(!lookup(".", "dir2", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__one_component);
+ATF_TEST_CASE_BODY(mkdir_p__one_component)
+{
+ ATF_REQUIRE(!lookup(".", "new-dir", S_IFDIR));
+ fs::mkdir_p(fs::path("new-dir"), 0755);
+ ATF_REQUIRE(lookup(".", "new-dir", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__many_components);
+ATF_TEST_CASE_BODY(mkdir_p__many_components)
+{
+ ATF_REQUIRE(!lookup(".", "a", S_IFDIR));
+ fs::mkdir_p(fs::path("a/b/c"), 0755);
+ ATF_REQUIRE(lookup(".", "a", S_IFDIR));
+ ATF_REQUIRE(lookup("a", "b", S_IFDIR));
+ ATF_REQUIRE(lookup("a/b", "c", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdir_p__already_exists);
+ATF_TEST_CASE_BODY(mkdir_p__already_exists)
+{
+ fs::mkdir(fs::path("a"), 0755);
+ fs::mkdir(fs::path("a/b"), 0755);
+ fs::mkdir_p(fs::path("a/b"), 0755);
+}
+
+
+ATF_TEST_CASE(mkdir_p__eacces)
+ATF_TEST_CASE_HEAD(mkdir_p__eacces)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(mkdir_p__eacces)
+{
+ fs::mkdir(fs::path("a"), 0755);
+ fs::mkdir(fs::path("a/b"), 0755);
+ ATF_REQUIRE(::chmod("a/b", 0555) != -1);
+ try {
+ fs::mkdir_p(fs::path("a/b/c/d"), 0755);
+ fail("system_error not raised");
+ } catch (const fs::system_error& e) {
+ ATF_REQUIRE_EQ(EACCES, e.original_errno());
+ }
+ ATF_REQUIRE(lookup(".", "a", S_IFDIR));
+ ATF_REQUIRE(lookup("a", "b", S_IFDIR));
+ ATF_REQUIRE(!lookup(".", "c", S_IFDIR));
+ ATF_REQUIRE(!lookup("a", "c", S_IFDIR));
+ ATF_REQUIRE(!lookup("a/b", "c", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkdtemp_public)
+ATF_TEST_CASE_BODY(mkdtemp_public)
+{
+ const fs::path tmpdir = fs::current_path() / "tmp";
+ utils::setenv("TMPDIR", tmpdir.str());
+ fs::mkdir(tmpdir, 0755);
+
+ const std::string dir_template("tempdir.XXXXXX");
+ const fs::path tempdir = fs::mkdtemp_public(dir_template);
+ ATF_REQUIRE(!lookup("tmp", dir_template.c_str(), S_IFDIR));
+ ATF_REQUIRE(lookup("tmp", tempdir.leaf_name().c_str(), S_IFDIR));
+}
+
+
+ATF_TEST_CASE(mkdtemp_public__getcwd_as_non_root)
+ATF_TEST_CASE_HEAD(mkdtemp_public__getcwd_as_non_root)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(mkdtemp_public__getcwd_as_non_root)
+{
+ const std::string dir_template("dir.XXXXXX");
+ const fs::path dir = fs::mkdtemp_public(dir_template);
+ const fs::path subdir = dir / "subdir";
+ fs::mkdir(subdir, 0755);
+
+ const uid_t old_euid = ::geteuid();
+ const gid_t old_egid = ::getegid();
+
+ const passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+ ATF_REQUIRE(::setegid(unprivileged_user.gid) != -1);
+ ATF_REQUIRE(::seteuid(unprivileged_user.uid) != -1);
+
+ // The next code block runs as non-root. We cannot use any ATF macros nor
+ // functions in it because a failure would cause the test to attempt to
+ // write to the ATF result file which may not be writable as non-root.
+ bool failed = false;
+ {
+ try {
+ if (::chdir(subdir.c_str()) == -1) {
+ std::cerr << "Cannot enter directory\n";
+ failed |= true;
+ } else {
+ fs::current_path();
+ }
+ } catch (const fs::error& e) {
+ failed |= true;
+ std::cerr << "Failed to query current path in: " << subdir << '\n';
+ }
+
+ if (::seteuid(old_euid) == -1) {
+ std::cerr << "Failed to restore euid; cannot continue\n";
+ std::abort();
+ }
+ if (::setegid(old_egid) == -1) {
+ std::cerr << "Failed to restore egid; cannot continue\n";
+ std::abort();
+ }
+ }
+
+ if (failed)
+ fail("Test failed; see stdout for details");
+}
+
+
+ATF_TEST_CASE(mkdtemp_public__search_permissions_as_non_root)
+ATF_TEST_CASE_HEAD(mkdtemp_public__search_permissions_as_non_root)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(mkdtemp_public__search_permissions_as_non_root)
+{
+ const std::string dir_template("dir.XXXXXX");
+ const fs::path dir = fs::mkdtemp_public(dir_template);
+ const fs::path cookie = dir / "not-secret";
+ atf::utils::create_file(cookie.str(), "this is readable");
+
+ // We are running as root so there is no reason to assume that our current
+ // work directory is accessible by non-root. Weaken the permissions so that
+ // our code below works.
+ ATF_REQUIRE(::chmod(".", 0755) != -1);
+
+ const uid_t old_euid = ::geteuid();
+ const gid_t old_egid = ::getegid();
+
+ const passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+ ATF_REQUIRE(::setegid(unprivileged_user.gid) != -1);
+ ATF_REQUIRE(::seteuid(unprivileged_user.uid) != -1);
+
+ // The next code block runs as non-root. We cannot use any ATF macros nor
+ // functions in it because a failure would cause the test to attempt to
+ // write to the ATF result file which may not be writable as non-root.
+ bool failed = false;
+ {
+ try {
+ const std::string contents = utils::read_file(cookie);
+ std::cerr << "Read contents: " << contents << '\n';
+ failed |= (contents != "this is readable");
+ } catch (const std::runtime_error& e) {
+ failed |= true;
+ std::cerr << "Failed to read " << cookie << '\n';
+ }
+
+ if (::seteuid(old_euid) == -1) {
+ std::cerr << "Failed to restore euid; cannot continue\n";
+ std::abort();
+ }
+ if (::setegid(old_egid) == -1) {
+ std::cerr << "Failed to restore egid; cannot continue\n";
+ std::abort();
+ }
+ }
+
+ if (failed)
+ fail("Test failed; see stdout for details");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(mkstemp)
+ATF_TEST_CASE_BODY(mkstemp)
+{
+ const fs::path tmpdir = fs::current_path() / "tmp";
+ utils::setenv("TMPDIR", tmpdir.str());
+ fs::mkdir(tmpdir, 0755);
+
+ const std::string file_template("tempfile.XXXXXX");
+ const fs::path tempfile = fs::mkstemp(file_template);
+ ATF_REQUIRE(!lookup("tmp", file_template.c_str(), S_IFREG));
+ ATF_REQUIRE(lookup("tmp", tempfile.leaf_name().c_str(), S_IFREG));
+}
+
+
+static void
+test_mount_tmpfs_ok(const units::bytes& size)
+{
+ const fs::path mount_point("mount_point");
+ fs::mkdir(mount_point, 0755);
+
+ try {
+ atf::utils::create_file("outside", "");
+ fs::mount_tmpfs(mount_point, size);
+ atf::utils::create_file("mounted", "");
+ atf::utils::create_file((mount_point / "inside").str(), "");
+
+ struct ::stat outside, inside;
+ ATF_REQUIRE(::stat("outside", &outside) != -1);
+ ATF_REQUIRE(::stat((mount_point / "inside").c_str(), &inside) != -1);
+ ATF_REQUIRE(outside.st_dev != inside.st_dev);
+ fs::unmount(mount_point);
+ } catch (const fs::unsupported_operation_error& e) {
+ ATF_SKIP(e.what());
+ }
+}
+
+
+ATF_TEST_CASE_WITH_CLEANUP(mount_tmpfs__ok__default_size)
+ATF_TEST_CASE_HEAD(mount_tmpfs__ok__default_size)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(mount_tmpfs__ok__default_size)
+{
+ test_mount_tmpfs_ok(units::bytes());
+}
+ATF_TEST_CASE_CLEANUP(mount_tmpfs__ok__default_size)
+{
+ cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
+}
+
+
+ATF_TEST_CASE_WITH_CLEANUP(mount_tmpfs__ok__explicit_size)
+ATF_TEST_CASE_HEAD(mount_tmpfs__ok__explicit_size)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(mount_tmpfs__ok__explicit_size)
+{
+ test_mount_tmpfs_ok(units::bytes(10 * units::MB));
+}
+ATF_TEST_CASE_CLEANUP(mount_tmpfs__ok__explicit_size)
+{
+ cleanup_mount_point(fs::path("mounted"), fs::path("mount_point"));
+}
+
+
+ATF_TEST_CASE(mount_tmpfs__fail)
+ATF_TEST_CASE_HEAD(mount_tmpfs__fail)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(mount_tmpfs__fail)
+{
+ try {
+ fs::mount_tmpfs(fs::path("non-existent"));
+ } catch (const fs::unsupported_operation_error& e) {
+ ATF_SKIP(e.what());
+ } catch (const fs::error& e) {
+ // Expected.
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(rm_r__empty);
+ATF_TEST_CASE_BODY(rm_r__empty)
+{
+ fs::mkdir(fs::path("root"), 0755);
+ ATF_REQUIRE(lookup(".", "root", S_IFDIR));
+ fs::rm_r(fs::path("root"));
+ ATF_REQUIRE(!lookup(".", "root", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(rm_r__files_and_directories);
+ATF_TEST_CASE_BODY(rm_r__files_and_directories)
+{
+ fs::mkdir(fs::path("root"), 0755);
+ atf::utils::create_file("root/.hidden_file", "");
+ fs::mkdir(fs::path("root/.hidden_dir"), 0755);
+ atf::utils::create_file("root/.hidden_dir/a", "");
+ atf::utils::create_file("root/file", "");
+ atf::utils::create_file("root/with spaces", "");
+ fs::mkdir(fs::path("root/dir1"), 0755);
+ fs::mkdir(fs::path("root/dir1/dir2"), 0755);
+ atf::utils::create_file("root/dir1/dir2/file", "");
+ fs::mkdir(fs::path("root/dir1/dir3"), 0755);
+ ATF_REQUIRE(lookup(".", "root", S_IFDIR));
+ fs::rm_r(fs::path("root"));
+ ATF_REQUIRE(!lookup(".", "root", S_IFDIR));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(rmdir__ok)
+ATF_TEST_CASE_BODY(rmdir__ok)
+{
+ ATF_REQUIRE(::mkdir("foo", 0755) != -1);
+ ATF_REQUIRE(::access("foo", X_OK) == 0);
+ fs::rmdir(fs::path("foo"));
+ ATF_REQUIRE(::access("foo", X_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(rmdir__fail)
+ATF_TEST_CASE_BODY(rmdir__fail)
+{
+ ATF_REQUIRE_THROW_RE(fs::system_error, "Removal of foo failed",
+ fs::rmdir(fs::path("foo")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(scan_directory__ok)
+ATF_TEST_CASE_BODY(scan_directory__ok)
+{
+ fs::mkdir(fs::path("dir"), 0755);
+ atf::utils::create_file("dir/foo", "");
+ atf::utils::create_file("dir/.hidden", "");
+
+ const std::set< fs::directory_entry > contents = fs::scan_directory(
+ fs::path("dir"));
+
+ std::set< fs::directory_entry > exp_contents;
+ exp_contents.insert(fs::directory_entry("."));
+ exp_contents.insert(fs::directory_entry(".."));
+ exp_contents.insert(fs::directory_entry(".hidden"));
+ exp_contents.insert(fs::directory_entry("foo"));
+
+ ATF_REQUIRE_EQ(exp_contents, contents);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(scan_directory__fail)
+ATF_TEST_CASE_BODY(scan_directory__fail)
+{
+ ATF_REQUIRE_THROW_RE(fs::system_error, "opendir(.*missing.*) failed",
+ fs::scan_directory(fs::path("missing")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unlink__ok)
+ATF_TEST_CASE_BODY(unlink__ok)
+{
+ atf::utils::create_file("foo", "");
+ ATF_REQUIRE(::access("foo", R_OK) == 0);
+ fs::unlink(fs::path("foo"));
+ ATF_REQUIRE(::access("foo", R_OK) == -1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unlink__fail)
+ATF_TEST_CASE_BODY(unlink__fail)
+{
+ ATF_REQUIRE_THROW_RE(fs::system_error, "Removal of foo failed",
+ fs::unlink(fs::path("foo")));
+}
+
+
+ATF_TEST_CASE(unmount__ok)
+ATF_TEST_CASE_HEAD(unmount__ok)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(unmount__ok)
+{
+ const fs::path mount_point("mount_point");
+ fs::mkdir(mount_point, 0755);
+
+ atf::utils::create_file((mount_point / "test1").str(), "");
+ try {
+ fs::mount_tmpfs(mount_point);
+ } catch (const fs::unsupported_operation_error& e) {
+ ATF_SKIP(e.what());
+ }
+
+ atf::utils::create_file((mount_point / "test2").str(), "");
+
+ ATF_REQUIRE(!fs::exists(mount_point / "test1"));
+ ATF_REQUIRE( fs::exists(mount_point / "test2"));
+ fs::unmount(mount_point);
+ ATF_REQUIRE( fs::exists(mount_point / "test1"));
+ ATF_REQUIRE(!fs::exists(mount_point / "test2"));
+}
+
+
+ATF_TEST_CASE(unmount__fail)
+ATF_TEST_CASE_HEAD(unmount__fail)
+{
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(unmount__fail)
+{
+ ATF_REQUIRE_THROW(fs::error, fs::unmount(fs::path("non-existent")));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, copy__ok);
+ ATF_ADD_TEST_CASE(tcs, copy__fail_open);
+ ATF_ADD_TEST_CASE(tcs, copy__fail_create);
+
+ ATF_ADD_TEST_CASE(tcs, current_path__ok);
+ ATF_ADD_TEST_CASE(tcs, current_path__enoent);
+
+ ATF_ADD_TEST_CASE(tcs, exists);
+
+ ATF_ADD_TEST_CASE(tcs, find_in_path__no_path);
+ ATF_ADD_TEST_CASE(tcs, find_in_path__empty_path);
+ ATF_ADD_TEST_CASE(tcs, find_in_path__one_component);
+ ATF_ADD_TEST_CASE(tcs, find_in_path__many_components);
+ ATF_ADD_TEST_CASE(tcs, find_in_path__current_directory);
+ ATF_ADD_TEST_CASE(tcs, find_in_path__always_absolute);
+
+ ATF_ADD_TEST_CASE(tcs, free_disk_space__ok__smoke);
+ ATF_ADD_TEST_CASE(tcs, free_disk_space__ok__real);
+ ATF_ADD_TEST_CASE(tcs, free_disk_space__fail);
+
+ ATF_ADD_TEST_CASE(tcs, is_directory__ok);
+ ATF_ADD_TEST_CASE(tcs, is_directory__fail);
+
+ ATF_ADD_TEST_CASE(tcs, mkdir__ok);
+ ATF_ADD_TEST_CASE(tcs, mkdir__enoent);
+
+ ATF_ADD_TEST_CASE(tcs, mkdir_p__one_component);
+ ATF_ADD_TEST_CASE(tcs, mkdir_p__many_components);
+ ATF_ADD_TEST_CASE(tcs, mkdir_p__already_exists);
+ ATF_ADD_TEST_CASE(tcs, mkdir_p__eacces);
+
+ ATF_ADD_TEST_CASE(tcs, mkdtemp_public);
+ ATF_ADD_TEST_CASE(tcs, mkdtemp_public__getcwd_as_non_root);
+ ATF_ADD_TEST_CASE(tcs, mkdtemp_public__search_permissions_as_non_root);
+
+ ATF_ADD_TEST_CASE(tcs, mkstemp);
+
+ ATF_ADD_TEST_CASE(tcs, mount_tmpfs__ok__default_size);
+ ATF_ADD_TEST_CASE(tcs, mount_tmpfs__ok__explicit_size);
+ ATF_ADD_TEST_CASE(tcs, mount_tmpfs__fail);
+
+ ATF_ADD_TEST_CASE(tcs, rm_r__empty);
+ ATF_ADD_TEST_CASE(tcs, rm_r__files_and_directories);
+
+ ATF_ADD_TEST_CASE(tcs, rmdir__ok);
+ ATF_ADD_TEST_CASE(tcs, rmdir__fail);
+
+ ATF_ADD_TEST_CASE(tcs, scan_directory__ok);
+ ATF_ADD_TEST_CASE(tcs, scan_directory__fail);
+
+ ATF_ADD_TEST_CASE(tcs, unlink__ok);
+ ATF_ADD_TEST_CASE(tcs, unlink__fail);
+
+ ATF_ADD_TEST_CASE(tcs, unmount__ok);
+ ATF_ADD_TEST_CASE(tcs, unmount__fail);
+}
diff --git a/utils/fs/path.cpp b/utils/fs/path.cpp
new file mode 100644
index 000000000000..465ed49c4c2a
--- /dev/null
+++ b/utils/fs/path.cpp
@@ -0,0 +1,303 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/path.hpp"
+
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/sanity.hpp"
+
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Normalizes an input string to a valid path.
+///
+/// A normalized path cannot have empty components; i.e. there can be at most
+/// one consecutive separator (/).
+///
+/// \param in The string to normalize.
+///
+/// \return The normalized string, representing a path.
+///
+/// \throw utils::fs::invalid_path_error If the path is empty.
+static std::string
+normalize(const std::string& in)
+{
+ if (in.empty())
+ throw fs::invalid_path_error(in, "Cannot be empty");
+
+ std::string out;
+
+ std::string::size_type pos = 0;
+ do {
+ const std::string::size_type next_pos = in.find('/', pos);
+
+ const std::string component = in.substr(pos, next_pos - pos);
+ if (!component.empty()) {
+ if (pos == 0)
+ out += component;
+ else if (component != ".")
+ out += "/" + component;
+ }
+
+ if (next_pos == std::string::npos)
+ pos = next_pos;
+ else
+ pos = next_pos + 1;
+ } while (pos != std::string::npos);
+
+ return out.empty() ? "/" : out;
+}
+
+
+} // anonymous namespace
+
+
+/// Creates a new path object from a textual representation of a path.
+///
+/// \param text A valid representation of a path in textual form.
+///
+/// \throw utils::fs::invalid_path_error If the input text does not represent a
+/// valid path.
+fs::path::path(const std::string& text) :
+ _repr(normalize(text))
+{
+}
+
+
+/// Gets a view of the path as an array of characters.
+///
+/// \return A \code const char* \endcode representation for the object.
+const char*
+fs::path::c_str(void) const
+{
+ return _repr.c_str();
+}
+
+
+/// Gets a view of the path as a std::string.
+///
+/// \return A \code std::string& \endcode representation for the object.
+const std::string&
+fs::path::str(void) const
+{
+ return _repr;
+}
+
+
+/// Gets the branch path (directory name) of the path.
+///
+/// The branch path of a path with just one component (no separators) is ".".
+///
+/// \return A new path representing the branch path.
+fs::path
+fs::path::branch_path(void) const
+{
+ const std::string::size_type end_pos = _repr.rfind('/');
+ if (end_pos == std::string::npos)
+ return fs::path(".");
+ else if (end_pos == 0)
+ return fs::path("/");
+ else
+ return fs::path(_repr.substr(0, end_pos));
+}
+
+
+/// Gets the leaf name (base name) of the path.
+///
+/// \return A new string representing the leaf name.
+std::string
+fs::path::leaf_name(void) const
+{
+ const std::string::size_type beg_pos = _repr.rfind('/');
+
+ if (beg_pos == std::string::npos)
+ return _repr;
+ else
+ return _repr.substr(beg_pos + 1);
+}
+
+
+/// Converts a relative path in the current directory to an absolute path.
+///
+/// \pre The path is relative.
+///
+/// \return The absolute representation of the relative path.
+fs::path
+fs::path::to_absolute(void) const
+{
+ PRE(!is_absolute());
+ return fs::current_path() / *this;
+}
+
+
+/// \return True if the representation of the path is absolute.
+bool
+fs::path::is_absolute(void) const
+{
+ return _repr[0] == '/';
+}
+
+
+/// Checks whether the path is a parent of another path.
+///
+/// A path is considered to be a parent of itself.
+///
+/// \return True if this path is a parent of p.
+bool
+fs::path::is_parent_of(path p) const
+{
+ do {
+ if ((*this) == p)
+ return true;
+ p = p.branch_path();
+ } while (p != fs::path(".") && p != fs::path("/"));
+ return false;
+}
+
+
+/// Counts the number of components in the path.
+///
+/// \return The number of components.
+int
+fs::path::ncomponents(void) const
+{
+ int count = 0;
+ if (_repr == "/")
+ return 1;
+ else {
+ for (std::string::const_iterator iter = _repr.begin();
+ iter != _repr.end(); ++iter) {
+ if (*iter == '/')
+ count++;
+ }
+ return count + 1;
+ }
+}
+
+
+/// Less-than comparator for paths.
+///
+/// This is provided to make identifiers useful as map keys.
+///
+/// \param p The path to compare to.
+///
+/// \return True if this identifier sorts before the other identifier; false
+/// otherwise.
+bool
+fs::path::operator<(const fs::path& p) const
+{
+ return _repr < p._repr;
+}
+
+
+/// Compares two paths for equality.
+///
+/// Given that the paths are internally normalized, input paths such as
+/// ///foo/bar and /foo///bar are exactly the same. However, this does NOT
+/// check for true equality: i.e. this does not access the file system to check
+/// if the paths actually point to the same object my means of links.
+///
+/// \param p The path to compare to.
+///
+/// \returns A boolean indicating whether the paths are equal.
+bool
+fs::path::operator==(const fs::path& p) const
+{
+ return _repr == p._repr;
+}
+
+
+/// Compares two paths for inequality.
+///
+/// See the description of operator==() for more details on the comparison
+/// performed.
+///
+/// \param p The path to compare to.
+///
+/// \returns A boolean indicating whether the paths are different.
+bool
+fs::path::operator!=(const fs::path& p) const
+{
+ return _repr != p._repr;
+}
+
+
+/// Concatenates this path with one or more components.
+///
+/// \param components The new components to concatenate to the path. These are
+/// normalized because, in general, they may come from user input. These
+/// components cannot represent an absolute path.
+///
+/// \return A new path containing the concatenation of this path and the
+/// provided components.
+///
+/// \throw utils::fs::invalid_path_error If components does not represent a
+/// valid path.
+/// \throw utils::fs::join_error If the join operation is invalid because the
+/// two paths are incompatible.
+fs::path
+fs::path::operator/(const std::string& components) const
+{
+ return (*this) / fs::path(components);
+}
+
+
+/// Concatenates this path with another path.
+///
+/// \param rest The path to concatenate to this one. Cannot be absolute.
+///
+/// \return A new path containing the concatenation of this path and the other
+/// path.
+///
+/// \throw utils::fs::join_error If the join operation is invalid because the
+/// two paths are incompatible.
+fs::path
+fs::path::operator/(const fs::path& rest) const
+{
+ if (rest.is_absolute())
+ throw fs::join_error(_repr, rest._repr,
+ "Cannot concatenate a path to an absolute path");
+ return fs::path(_repr + '/' + rest._repr);
+}
+
+
+/// Formats a path for insertion on a stream.
+///
+/// \param os The output stream.
+/// \param p The path to inject to the stream.
+///
+/// \return The output stream os.
+std::ostream&
+fs::operator<<(std::ostream& os, const fs::path& p)
+{
+ return (os << p.str());
+}
diff --git a/utils/fs/path.hpp b/utils/fs/path.hpp
new file mode 100644
index 000000000000..fe55fd55f234
--- /dev/null
+++ b/utils/fs/path.hpp
@@ -0,0 +1,87 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/path.hpp
+/// Provides the utils::fs::path class.
+///
+/// This is a poor man's reimplementation of the path class provided by
+/// Boost.Filesystem, in the sense that it tries to follow the same API but is
+/// much simplified.
+
+#if !defined(UTILS_FS_PATH_HPP)
+#define UTILS_FS_PATH_HPP
+
+#include "utils/fs/path_fwd.hpp"
+
+#include <string>
+#include <ostream>
+
+namespace utils {
+namespace fs {
+
+
+/// Representation and manipulation of a file system path.
+///
+/// Application code should always use this class to represent a path instead of
+/// std::string, because this class is more semantically representative, ensures
+/// that the values are valid and provides some useful manipulation functions.
+///
+/// Conversions to and from strings are always explicit.
+class path {
+ /// Internal representation of the path.
+ std::string _repr;
+
+public:
+ explicit path(const std::string&);
+
+ const char* c_str(void) const;
+ const std::string& str(void) const;
+
+ path branch_path(void) const;
+ std::string leaf_name(void) const;
+ path to_absolute(void) const;
+
+ bool is_absolute(void) const;
+ bool is_parent_of(path) const;
+ int ncomponents(void) const;
+
+ bool operator<(const path&) const;
+ bool operator==(const path&) const;
+ bool operator!=(const path&) const;
+ path operator/(const std::string&) const;
+ path operator/(const path&) const;
+};
+
+
+std::ostream& operator<<(std::ostream&, const path&);
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_PATH_HPP)
diff --git a/utils/fs/path_fwd.hpp b/utils/fs/path_fwd.hpp
new file mode 100644
index 000000000000..4e6856073553
--- /dev/null
+++ b/utils/fs/path_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/fs/path_fwd.hpp
+/// Forward declarations for utils/fs/path.hpp
+
+#if !defined(UTILS_FS_PATH_FWD_HPP)
+#define UTILS_FS_PATH_FWD_HPP
+
+namespace utils {
+namespace fs {
+
+
+class path;
+
+
+} // namespace fs
+} // namespace utils
+
+#endif // !defined(UTILS_FS_PATH_FWD_HPP)
diff --git a/utils/fs/path_test.cpp b/utils/fs/path_test.cpp
new file mode 100644
index 000000000000..30ad3110de31
--- /dev/null
+++ b/utils/fs/path_test.cpp
@@ -0,0 +1,277 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/fs/path.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <set>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/exceptions.hpp"
+
+using utils::fs::invalid_path_error;
+using utils::fs::join_error;
+using utils::fs::path;
+
+
+#define REQUIRE_JOIN_ERROR(path1, path2, expr) \
+ try { \
+ expr; \
+ ATF_FAIL("Expecting join_error but no error raised"); \
+ } catch (const join_error& e) { \
+ ATF_REQUIRE_EQ(path1, e.textual_path1()); \
+ ATF_REQUIRE_EQ(path2, e.textual_path2()); \
+ }
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(normalize__ok);
+ATF_TEST_CASE_BODY(normalize__ok)
+{
+ ATF_REQUIRE_EQ(".", path(".").str());
+ ATF_REQUIRE_EQ("..", path("..").str());
+ ATF_REQUIRE_EQ("/", path("/").str());
+ ATF_REQUIRE_EQ("/", path("///").str());
+
+ ATF_REQUIRE_EQ("foo", path("foo").str());
+ ATF_REQUIRE_EQ("foo/bar", path("foo/bar").str());
+ ATF_REQUIRE_EQ("foo/bar", path("foo/bar/").str());
+
+ ATF_REQUIRE_EQ("/foo", path("/foo").str());
+ ATF_REQUIRE_EQ("/foo/bar", path("/foo/bar").str());
+ ATF_REQUIRE_EQ("/foo/bar", path("/foo/bar/").str());
+
+ ATF_REQUIRE_EQ("/foo", path("///foo").str());
+ ATF_REQUIRE_EQ("/foo/bar", path("///foo///bar").str());
+ ATF_REQUIRE_EQ("/foo/bar", path("///foo///bar///").str());
+
+ ATF_REQUIRE_EQ("./foo/bar", path("./foo/bar").str());
+ ATF_REQUIRE_EQ("./foo/bar", path("./foo/./bar").str());
+ ATF_REQUIRE_EQ("./foo/bar", path("././foo/./bar").str());
+ ATF_REQUIRE_EQ("foo/bar", path("foo/././bar").str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(normalize__invalid);
+ATF_TEST_CASE_BODY(normalize__invalid)
+{
+ try {
+ path("");
+ fail("invalid_path_error not raised");
+ } catch (const invalid_path_error& e) {
+ ATF_REQUIRE(e.invalid_path().empty());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_absolute);
+ATF_TEST_CASE_BODY(is_absolute)
+{
+ ATF_REQUIRE( path("/").is_absolute());
+ ATF_REQUIRE( path("////").is_absolute());
+ ATF_REQUIRE( path("////a").is_absolute());
+ ATF_REQUIRE( path("//a//").is_absolute());
+ ATF_REQUIRE(!path("a////").is_absolute());
+ ATF_REQUIRE(!path("../foo").is_absolute());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_parent_of);
+ATF_TEST_CASE_BODY(is_parent_of)
+{
+ ATF_REQUIRE( path("/").is_parent_of(path("/")));
+ ATF_REQUIRE( path(".").is_parent_of(path(".")));
+ ATF_REQUIRE( path("/a").is_parent_of(path("/a")));
+ ATF_REQUIRE( path("/a/b/c").is_parent_of(path("/a/b/c")));
+ ATF_REQUIRE( path("a").is_parent_of(path("a")));
+ ATF_REQUIRE( path("a/b/c").is_parent_of(path("a/b/c")));
+
+ ATF_REQUIRE( path("/a/b/c").is_parent_of(path("/a/b/c/d")));
+ ATF_REQUIRE( path("/a/b/c").is_parent_of(path("/a/b/c/d/e")));
+ ATF_REQUIRE(!path("/a/b/c").is_parent_of(path("a/b/c")));
+ ATF_REQUIRE(!path("/a/b/c").is_parent_of(path("a/b/c/d/e")));
+
+ ATF_REQUIRE( path("a/b/c").is_parent_of(path("a/b/c/d")));
+ ATF_REQUIRE( path("a/b/c").is_parent_of(path("a/b/c/d/e")));
+ ATF_REQUIRE(!path("a/b/c").is_parent_of(path("/a/b/c")));
+ ATF_REQUIRE(!path("a/b/c").is_parent_of(path("/a/b/c/d/e")));
+
+ ATF_REQUIRE(!path("/a/b/c/d/e").is_parent_of(path("/a/b/c")));
+ ATF_REQUIRE(!path("/a/b/c/d/e").is_parent_of(path("a/b/c")));
+ ATF_REQUIRE(!path("a/b/c/d/e").is_parent_of(path("/a/b/c")));
+ ATF_REQUIRE(!path("a/b/c/d/e").is_parent_of(path("a/b/c")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ncomponents);
+ATF_TEST_CASE_BODY(ncomponents)
+{
+ ATF_REQUIRE_EQ(1, path(".").ncomponents());
+ ATF_REQUIRE_EQ(1, path("/").ncomponents());
+
+ ATF_REQUIRE_EQ(1, path("abc").ncomponents());
+ ATF_REQUIRE_EQ(1, path("abc/").ncomponents());
+
+ ATF_REQUIRE_EQ(2, path("/abc").ncomponents());
+ ATF_REQUIRE_EQ(3, path("/abc/def").ncomponents());
+
+ ATF_REQUIRE_EQ(2, path("abc/def").ncomponents());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(branch_path);
+ATF_TEST_CASE_BODY(branch_path)
+{
+ ATF_REQUIRE_EQ(".", path(".").branch_path().str());
+ ATF_REQUIRE_EQ(".", path("foo").branch_path().str());
+ ATF_REQUIRE_EQ("foo", path("foo/bar").branch_path().str());
+ ATF_REQUIRE_EQ("/", path("/foo").branch_path().str());
+ ATF_REQUIRE_EQ("/foo", path("/foo/bar").branch_path().str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(leaf_name);
+ATF_TEST_CASE_BODY(leaf_name)
+{
+ ATF_REQUIRE_EQ(".", path(".").leaf_name());
+ ATF_REQUIRE_EQ("foo", path("foo").leaf_name());
+ ATF_REQUIRE_EQ("bar", path("foo/bar").leaf_name());
+ ATF_REQUIRE_EQ("foo", path("/foo").leaf_name());
+ ATF_REQUIRE_EQ("bar", path("/foo/bar").leaf_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_absolute);
+ATF_TEST_CASE_BODY(to_absolute)
+{
+ ATF_REQUIRE(::chdir("/bin") != -1);
+ const std::string absolute = path("ls").to_absolute().str();
+ // In some systems (e.g. in Fedora 17), /bin is really a symlink to
+ // /usr/bin. Doing an explicit match of 'absolute' to /bin/ls fails in such
+ // case. Instead, attempt doing a search in the generated path just for a
+ // substring containing '/bin/ls'. Note that this can still fail if /bin is
+ // linked to something arbitrary like /a/b... but let's just assume this
+ // does not happen.
+ ATF_REQUIRE(absolute.find("/bin/ls") != std::string::npos);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(compare_less_than);
+ATF_TEST_CASE_BODY(compare_less_than)
+{
+ ATF_REQUIRE(!(path("/") < path("/")));
+ ATF_REQUIRE(!(path("/") < path("///")));
+
+ ATF_REQUIRE(!(path("/a/b/c") < path("/a/b/c")));
+
+ ATF_REQUIRE( path("/a") < path("/b"));
+ ATF_REQUIRE(!(path("/b") < path("/a")));
+
+ ATF_REQUIRE( path("/a") < path("/aa"));
+ ATF_REQUIRE(!(path("/aa") < path("/a")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(compare_equal);
+ATF_TEST_CASE_BODY(compare_equal)
+{
+ ATF_REQUIRE(path("/") == path("///"));
+ ATF_REQUIRE(path("/a") == path("///a"));
+ ATF_REQUIRE(path("/a") == path("///a///"));
+
+ ATF_REQUIRE(path("a/b/c") == path("a//b//c"));
+ ATF_REQUIRE(path("a/b/c") == path("a//b//c///"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(compare_different);
+ATF_TEST_CASE_BODY(compare_different)
+{
+ ATF_REQUIRE(path("/") != path("//a/"));
+ ATF_REQUIRE(path("/a") != path("a///"));
+
+ ATF_REQUIRE(path("a/b/c") != path("a/b"));
+ ATF_REQUIRE(path("a/b/c") != path("a//b"));
+ ATF_REQUIRE(path("a/b/c") != path("/a/b/c"));
+ ATF_REQUIRE(path("a/b/c") != path("/a//b//c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(concat__to_string);
+ATF_TEST_CASE_BODY(concat__to_string)
+{
+ ATF_REQUIRE_EQ("foo/bar", (path("foo") / "bar").str());
+ ATF_REQUIRE_EQ("foo/bar", (path("foo/") / "bar").str());
+ ATF_REQUIRE_EQ("foo/bar/baz", (path("foo/") / "bar//baz///").str());
+
+ ATF_REQUIRE_THROW(invalid_path_error, path("foo") / "");
+ REQUIRE_JOIN_ERROR("foo", "/a/b", path("foo") / "/a/b");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(concat__to_path);
+ATF_TEST_CASE_BODY(concat__to_path)
+{
+ ATF_REQUIRE_EQ("foo/bar", (path("foo") / "bar").str());
+ ATF_REQUIRE_EQ("foo/bar", (path("foo/") / "bar").str());
+ ATF_REQUIRE_EQ("foo/bar/baz", (path("foo/") / "bar//baz///").str());
+
+ REQUIRE_JOIN_ERROR("foo", "/a/b", path("foo") / path("/a/b"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(use_as_key);
+ATF_TEST_CASE_BODY(use_as_key)
+{
+ std::set< path > paths;
+ paths.insert(path("/a"));
+ ATF_REQUIRE(paths.find(path("//a")) != paths.end());
+ ATF_REQUIRE(paths.find(path("a")) == paths.end());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, normalize__ok);
+ ATF_ADD_TEST_CASE(tcs, normalize__invalid);
+ ATF_ADD_TEST_CASE(tcs, is_absolute);
+ ATF_ADD_TEST_CASE(tcs, is_parent_of);
+ ATF_ADD_TEST_CASE(tcs, ncomponents);
+ ATF_ADD_TEST_CASE(tcs, branch_path);
+ ATF_ADD_TEST_CASE(tcs, leaf_name);
+ ATF_ADD_TEST_CASE(tcs, to_absolute);
+ ATF_ADD_TEST_CASE(tcs, compare_less_than);
+ ATF_ADD_TEST_CASE(tcs, compare_equal);
+ ATF_ADD_TEST_CASE(tcs, compare_different);
+ ATF_ADD_TEST_CASE(tcs, concat__to_string);
+ ATF_ADD_TEST_CASE(tcs, concat__to_path);
+ ATF_ADD_TEST_CASE(tcs, use_as_key);
+}
diff --git a/utils/logging/Kyuafile b/utils/logging/Kyuafile
new file mode 100644
index 000000000000..0853a335c6ae
--- /dev/null
+++ b/utils/logging/Kyuafile
@@ -0,0 +1,6 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="macros_test"}
+atf_test_program{name="operations_test"}
diff --git a/utils/logging/Makefile.am.inc b/utils/logging/Makefile.am.inc
new file mode 100644
index 000000000000..7d88f16859d7
--- /dev/null
+++ b/utils/logging/Makefile.am.inc
@@ -0,0 +1,53 @@
+# Copyright 2011 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(LUTOK_CFLAGS)
+UTILS_LIBS += $(LUTOK_LIBS)
+
+libutils_a_CPPFLAGS += $(LUTOK_CFLAGS)
+libutils_a_SOURCES += utils/logging/macros.hpp
+libutils_a_SOURCES += utils/logging/operations.cpp
+libutils_a_SOURCES += utils/logging/operations.hpp
+libutils_a_SOURCES += utils/logging/operations_fwd.hpp
+
+if WITH_ATF
+tests_utils_loggingdir = $(pkgtestsdir)/utils/logging
+
+tests_utils_logging_DATA = utils/logging/Kyuafile
+EXTRA_DIST += $(tests_utils_logging_DATA)
+
+tests_utils_logging_PROGRAMS = utils/logging/macros_test
+utils_logging_macros_test_SOURCES = utils/logging/macros_test.cpp
+utils_logging_macros_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_logging_macros_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_logging_PROGRAMS += utils/logging/operations_test
+utils_logging_operations_test_SOURCES = utils/logging/operations_test.cpp
+utils_logging_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_logging_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/logging/macros.hpp b/utils/logging/macros.hpp
new file mode 100644
index 000000000000..73dd0a60ef87
--- /dev/null
+++ b/utils/logging/macros.hpp
@@ -0,0 +1,68 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/logging/macros.hpp
+/// Convenience macros to simplify usage of the logging library.
+///
+/// This file <em>must not be included from other header files</em>.
+
+#if !defined(UTILS_LOGGING_MACROS_HPP)
+#define UTILS_LOGGING_MACROS_HPP
+
+#include "utils/logging/operations.hpp"
+
+
+/// Logs a debug message.
+///
+/// \param message The message to log.
+#define LD(message) utils::logging::log(utils::logging::level_debug, \
+ __FILE__, __LINE__, message)
+
+
+/// Logs an error message.
+///
+/// \param message The message to log.
+#define LE(message) utils::logging::log(utils::logging::level_error, \
+ __FILE__, __LINE__, message)
+
+
+/// Logs an informational message.
+///
+/// \param message The message to log.
+#define LI(message) utils::logging::log(utils::logging::level_info, \
+ __FILE__, __LINE__, message)
+
+
+/// Logs a warning message.
+///
+/// \param message The message to log.
+#define LW(message) utils::logging::log(utils::logging::level_warning, \
+ __FILE__, __LINE__, message)
+
+
+#endif // !defined(UTILS_LOGGING_MACROS_HPP)
diff --git a/utils/logging/macros_test.cpp b/utils/logging/macros_test.cpp
new file mode 100644
index 000000000000..fe3ee63cd533
--- /dev/null
+++ b/utils/logging/macros_test.cpp
@@ -0,0 +1,115 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/logging/macros.hpp"
+
+#include <fstream>
+#include <string>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/operations.hpp"
+
+namespace datetime = utils::datetime;
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ld);
+ATF_TEST_CASE_BODY(ld)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+ datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0);
+ LD("Debug message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_MATCH("20110221-183000 D .*: Debug message", line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(le);
+ATF_TEST_CASE_BODY(le)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+ datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0);
+ LE("Error message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_MATCH("20110221-183000 E .*: Error message", line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(li);
+ATF_TEST_CASE_BODY(li)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+ datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0);
+ LI("Info message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_MATCH("20110221-183000 I .*: Info message", line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lw);
+ATF_TEST_CASE_BODY(lw)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+ datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0);
+ LW("Warning message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_MATCH("20110221-183000 W .*: Warning message", line);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, ld);
+ ATF_ADD_TEST_CASE(tcs, le);
+ ATF_ADD_TEST_CASE(tcs, li);
+ ATF_ADD_TEST_CASE(tcs, lw);
+}
diff --git a/utils/logging/operations.cpp b/utils/logging/operations.cpp
new file mode 100644
index 000000000000..88f25361fa18
--- /dev/null
+++ b/utils/logging/operations.cpp
@@ -0,0 +1,303 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/logging/operations.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "utils/datetime.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/stream.hpp"
+
+namespace datetime = utils::datetime;
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+
+using utils::none;
+using utils::optional;
+
+
+/// The general idea for the application-wide logging goes like this:
+///
+/// 1. The application starts. Logging is initialized to capture _all_ log
+/// messages into memory regardless of their level by issuing a call to the
+/// set_inmemory() function.
+///
+/// 2. The application offers the user a way to select the logging level and a
+/// file into which to store the log.
+///
+/// 3. The application calls set_persistency providing a new log level and a log
+/// file. This must be done as early as possible, to minimize the chances of an
+/// early crash not capturing any logs.
+///
+/// 4. At this point, any log messages stored into memory are flushed to disk
+/// respecting the provided log level.
+///
+/// 5. The internal state of the logging module is updated to only capture
+/// messages that are of the provided log level (or below) and is configured to
+/// directly send messages to disk.
+///
+/// 6. The user may choose to call set_inmemory() again at a later stage, which
+/// will cause the log to be flushed and messages to be recorded in memory
+/// again. This is useful in case the logs are being sent to either stdout or
+/// stderr and the process forks and wants to keep those child channels
+/// unpolluted.
+///
+/// The call to set_inmemory() should only be performed by the user-facing
+/// application. Tests should skip this call so that the logging messages go to
+/// stderr by default, thus generating a useful log to debug the tests.
+
+
+namespace {
+
+
+/// Constant string to strftime to format timestamps.
+static const char* timestamp_format = "%Y%m%d-%H%M%S";
+
+
+/// Mutable global state.
+struct global_state {
+ /// Current log level.
+ logging::level log_level;
+
+ /// Indicates whether set_persistency() will be called automatically or not.
+ bool auto_set_persistency;
+
+ /// First time recorded by the logging module.
+ optional< datetime::timestamp > first_timestamp;
+
+ /// In-memory record of log entries before persistency is enabled.
+ std::vector< std::pair< logging::level, std::string > > backlog;
+
+ /// Stream to the currently open log file.
+ std::auto_ptr< std::ostream > logfile;
+
+ global_state() :
+ log_level(logging::level_debug),
+ auto_set_persistency(true)
+ {
+ }
+};
+
+
+/// Single instance of the mutable global state.
+///
+/// Note that this is a raw pointer that we intentionally leak. We must do
+/// this, instead of making all of the singleton's members static values,
+/// because we want other destructors in the program to be able to log critical
+/// conditions. If we use complex types in this translation unit, they may be
+/// destroyed before the logging methods in the destructors get a chance to run
+/// thus resulting in a premature crash. By using a plain pointer, we ensure
+/// this state never gets cleaned up.
+static struct global_state* globals_singleton = NULL;
+
+
+/// Gets the singleton instance of global_state.
+///
+/// \return A pointer to the unique global_state instance.
+static struct global_state*
+get_globals(void)
+{
+ if (globals_singleton == NULL) {
+ globals_singleton = new global_state();
+ }
+ return globals_singleton;
+}
+
+
+/// Converts a level to a printable character.
+///
+/// \param level The level to convert.
+///
+/// \return The printable character, to be used in log messages.
+static char
+level_to_char(const logging::level level)
+{
+ switch (level) {
+ case logging::level_error: return 'E';
+ case logging::level_warning: return 'W';
+ case logging::level_info: return 'I';
+ case logging::level_debug: return 'D';
+ default: UNREACHABLE;
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Generates a standard log name.
+///
+/// This always adds the same timestamp to the log name for a particular run.
+/// Also, the timestamp added to the file name corresponds to the first
+/// timestamp recorded by the module; it does not necessarily contain the
+/// current value of "now".
+///
+/// \param logdir The path to the directory in which to place the log.
+/// \param progname The name of the program that is generating the log.
+///
+/// \return A string representation of the log name based on \p logdir and
+/// \p progname.
+fs::path
+logging::generate_log_name(const fs::path& logdir, const std::string& progname)
+{
+ struct global_state* globals = get_globals();
+
+ if (!globals->first_timestamp)
+ globals->first_timestamp = datetime::timestamp::now();
+ // Update kyua(1) if you change the name format.
+ return logdir / (F("%s.%s.log") % progname %
+ globals->first_timestamp.get().strftime(timestamp_format));
+}
+
+
+/// Logs an entry to the log file.
+///
+/// If the log is not yet set to persistent mode, the entry is recorded in the
+/// in-memory backlog. Otherwise, it is just written to disk.
+///
+/// \param message_level The level of the entry.
+/// \param file The file from which the log message is generated.
+/// \param line The line from which the log message is generated.
+/// \param user_message The raw message to store.
+void
+logging::log(const level message_level, const char* file, const int line,
+ const std::string& user_message)
+{
+ struct global_state* globals = get_globals();
+
+ const datetime::timestamp now = datetime::timestamp::now();
+ if (!globals->first_timestamp)
+ globals->first_timestamp = now;
+
+ if (globals->auto_set_persistency) {
+ // These values are hardcoded here for testing purposes. The
+ // application should call set_inmemory() by itself during
+ // initialization to avoid this, so that it has explicit control on how
+ // the call to set_persistency() happens.
+ set_persistency("debug", fs::path("/dev/stderr"));
+ globals->auto_set_persistency = false;
+ }
+
+ if (message_level > globals->log_level)
+ return;
+
+ // Update doc/troubleshooting.texi if you change the log format.
+ const std::string message = F("%s %s %s %s:%s: %s") %
+ now.strftime(timestamp_format) % level_to_char(message_level) %
+ ::getpid() % file % line % user_message;
+ if (globals->logfile.get() == NULL)
+ globals->backlog.push_back(std::make_pair(message_level, message));
+ else {
+ INV(globals->backlog.empty());
+ (*globals->logfile) << message << '\n';
+ globals->logfile->flush();
+ }
+}
+
+
+/// Sets the logging to record messages in memory for later flushing.
+///
+/// Can be called after set_persistency to flush logs and set recording to be
+/// in-memory again.
+void
+logging::set_inmemory(void)
+{
+ struct global_state* globals = get_globals();
+
+ globals->auto_set_persistency = false;
+
+ if (globals->logfile.get() != NULL) {
+ INV(globals->backlog.empty());
+ globals->logfile->flush();
+ globals->logfile.reset(NULL);
+ }
+}
+
+
+/// Makes the log persistent.
+///
+/// Calling this function flushes the in-memory log, if any, to disk and sets
+/// the logging module to send log entries to disk from this point onwards.
+/// There is no way back, and the caller program should execute this function as
+/// early as possible to ensure that a crash at startup does not discard too
+/// many useful log entries.
+///
+/// Any log entries above the provided new_level are discarded.
+///
+/// \param new_level The new log level.
+/// \param path The file to write the logs to.
+///
+/// \throw std::range_error If the given log level is invalid.
+/// \throw std::runtime_error If the given file cannot be created.
+void
+logging::set_persistency(const std::string& new_level, const fs::path& path)
+{
+ struct global_state* globals = get_globals();
+
+ globals->auto_set_persistency = false;
+
+ PRE(globals->logfile.get() == NULL);
+
+ // Update doc/troubleshooting.info if you change the log levels.
+ if (new_level == "debug")
+ globals->log_level = level_debug;
+ else if (new_level == "error")
+ globals->log_level = level_error;
+ else if (new_level == "info")
+ globals->log_level = level_info;
+ else if (new_level == "warning")
+ globals->log_level = level_warning;
+ else
+ throw std::range_error(F("Unrecognized log level '%s'") % new_level);
+
+ try {
+ globals->logfile = utils::open_ostream(path);
+ } catch (const std::runtime_error& unused_error) {
+ throw std::runtime_error(F("Failed to create log file %s") % path);
+ }
+
+ for (std::vector< std::pair< logging::level, std::string > >::const_iterator
+ iter = globals->backlog.begin(); iter != globals->backlog.end();
+ ++iter) {
+ if ((*iter).first <= globals->log_level)
+ (*globals->logfile) << (*iter).second << '\n';
+ }
+ globals->logfile->flush();
+ globals->backlog.clear();
+}
diff --git a/utils/logging/operations.hpp b/utils/logging/operations.hpp
new file mode 100644
index 000000000000..1bb72219dcae
--- /dev/null
+++ b/utils/logging/operations.hpp
@@ -0,0 +1,54 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/logging/operations.hpp
+/// Stateless logging facilities.
+
+#if !defined(UTILS_LOGGING_OPERATIONS_HPP)
+#define UTILS_LOGGING_OPERATIONS_HPP
+
+#include "utils/logging/operations_fwd.hpp"
+
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace logging {
+
+
+fs::path generate_log_name(const fs::path&, const std::string&);
+void log(const level, const char*, const int, const std::string&);
+void set_inmemory(void);
+void set_persistency(const std::string&, const fs::path&);
+
+
+} // namespace logging
+} // namespace utils
+
+#endif // !defined(UTILS_LOGGING_OPERATIONS_HPP)
diff --git a/utils/logging/operations_fwd.hpp b/utils/logging/operations_fwd.hpp
new file mode 100644
index 000000000000..0e3edd7993ec
--- /dev/null
+++ b/utils/logging/operations_fwd.hpp
@@ -0,0 +1,54 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/logging/operations_fwd.hpp
+/// Forward declarations for utils/logging/operations.hpp
+
+#if !defined(UTILS_LOGGING_OPERATIONS_FWD_HPP)
+#define UTILS_LOGGING_OPERATIONS_FWD_HPP
+
+namespace utils {
+namespace logging {
+
+
+/// Severity levels for log messages.
+///
+/// This enumeration must be sorted from the most severe message to the least
+/// severe.
+enum level {
+ level_error = 0,
+ level_warning,
+ level_info,
+ level_debug,
+};
+
+
+} // namespace logging
+} // namespace utils
+
+#endif // !defined(UTILS_LOGGING_OPERATIONS_FWD_HPP)
diff --git a/utils/logging/operations_test.cpp b/utils/logging/operations_test.cpp
new file mode 100644
index 000000000000..402f36e62904
--- /dev/null
+++ b/utils/logging/operations_test.cpp
@@ -0,0 +1,354 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/logging/operations.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <fstream>
+#include <string>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+
+namespace datetime = utils::datetime;
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(generate_log_name__before_log);
+ATF_TEST_CASE_BODY(generate_log_name__before_log)
+{
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 0, 0);
+ ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181000.log"),
+ logging::generate_log_name(fs::path("/some/dir"), "foobar"));
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 1, 987654);
+ logging::log(logging::level_info, "file", 123, "A message");
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 2, 123);
+ ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181000.log"),
+ logging::generate_log_name(fs::path("/some/dir"), "foobar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(generate_log_name__after_log);
+ATF_TEST_CASE_BODY(generate_log_name__after_log)
+{
+ datetime::set_mock_now(2011, 2, 21, 18, 15, 0, 0);
+ logging::log(logging::level_info, "file", 123, "A message");
+ datetime::set_mock_now(2011, 2, 21, 18, 15, 1, 987654);
+ logging::log(logging::level_info, "file", 123, "A message");
+
+ datetime::set_mock_now(2011, 2, 21, 18, 15, 2, 123);
+ ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181500.log"),
+ logging::generate_log_name(fs::path("/some/dir"), "foobar"));
+
+ datetime::set_mock_now(2011, 2, 21, 18, 15, 3, 1);
+ logging::log(logging::level_info, "file", 123, "A message");
+
+ datetime::set_mock_now(2011, 2, 21, 18, 15, 4, 91);
+ ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181500.log"),
+ logging::generate_log_name(fs::path("/some/dir"), "foobar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(log);
+ATF_TEST_CASE_BODY(log)
+{
+ logging::set_inmemory();
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 0, 0);
+ logging::log(logging::level_debug, "f1", 1, "Debug message");
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 1, 987654);
+ logging::log(logging::level_error, "f2", 2, "Error message");
+
+ logging::set_persistency("debug", fs::path("test.log"));
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 2, 123);
+ logging::log(logging::level_info, "f3", 3, "Info message");
+
+ datetime::set_mock_now(2011, 2, 21, 18, 10, 3, 456);
+ logging::log(logging::level_warning, "f4", 4, "Warning message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-181000 D %s f1:1: Debug message") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-181001 E %s f2:2: Error message") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-181002 I %s f3:3: Info message") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-181003 W %s f4:4: Warning message") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_inmemory__reset);
+ATF_TEST_CASE_BODY(set_inmemory__reset)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+
+ datetime::set_mock_now(2011, 2, 21, 18, 20, 0, 654321);
+ logging::log(logging::level_debug, "file", 123, "Debug message");
+ logging::set_inmemory();
+ logging::log(logging::level_debug, "file", 123, "Debug message 2");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-182000 D %s file:123: Debug message") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__no_backlog);
+ATF_TEST_CASE_BODY(set_persistency__no_backlog)
+{
+ logging::set_persistency("debug", fs::path("test.log"));
+
+ datetime::set_mock_now(2011, 2, 21, 18, 20, 0, 654321);
+ logging::log(logging::level_debug, "file", 123, "Debug message");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110221-182000 D %s file:123: Debug message") % pid).str(), line);
+}
+
+
+/// Creates a log for testing purposes, buffering messages on start.
+///
+/// \param level The level of the desired log.
+/// \param path The output file.
+static void
+create_log(const std::string& level, const std::string& path)
+{
+ logging::set_inmemory();
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 0, 100);
+ logging::log(logging::level_debug, "file1", 11, "Debug 1");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 1, 200);
+ logging::log(logging::level_error, "file2", 22, "Error 1");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 2, 300);
+ logging::log(logging::level_info, "file3", 33, "Info 1");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 3, 400);
+ logging::log(logging::level_warning, "file4", 44, "Warning 1");
+
+ logging::set_persistency(level, fs::path(path));
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 4, 500);
+ logging::log(logging::level_debug, "file1", 11, "Debug 2");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 5, 600);
+ logging::log(logging::level_error, "file2", 22, "Error 2");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 6, 700);
+ logging::log(logging::level_info, "file3", 33, "Info 2");
+
+ datetime::set_mock_now(2011, 3, 19, 11, 40, 7, 800);
+ logging::log(logging::level_warning, "file4", 44, "Warning 2");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__debug);
+ATF_TEST_CASE_BODY(set_persistency__some_backlog__debug)
+{
+ create_log("debug", "test.log");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114000 D %s file1:11: Debug 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114002 I %s file3:33: Info 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114004 D %s file1:11: Debug 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114006 I %s file3:33: Info 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__error);
+ATF_TEST_CASE_BODY(set_persistency__some_backlog__error)
+{
+ create_log("error", "test.log");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__info);
+ATF_TEST_CASE_BODY(set_persistency__some_backlog__info)
+{
+ create_log("info", "test.log");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114002 I %s file3:33: Info 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114006 I %s file3:33: Info 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__warning);
+ATF_TEST_CASE_BODY(set_persistency__some_backlog__warning)
+{
+ create_log("warning", "test.log");
+
+ std::ifstream input("test.log");
+ ATF_REQUIRE(input);
+
+ const pid_t pid = ::getpid();
+
+ std::string line;
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line);
+ ATF_REQUIRE(std::getline(input, line).good());
+ ATF_REQUIRE_EQ(
+ (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line);
+}
+
+
+ATF_TEST_CASE(set_persistency__fail);
+ATF_TEST_CASE_HEAD(set_persistency__fail)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(set_persistency__fail)
+{
+ ATF_REQUIRE_THROW_RE(std::range_error, "'foobar'",
+ logging::set_persistency("foobar", fs::path("log")));
+
+ fs::mkdir(fs::path("dir"), 0644);
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "dir/fail.log",
+ logging::set_persistency("debug",
+ fs::path("dir/fail.log")));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, generate_log_name__before_log);
+ ATF_ADD_TEST_CASE(tcs, generate_log_name__after_log);
+
+ ATF_ADD_TEST_CASE(tcs, log);
+
+ ATF_ADD_TEST_CASE(tcs, set_inmemory__reset);
+
+ ATF_ADD_TEST_CASE(tcs, set_persistency__no_backlog);
+ ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__debug);
+ ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__error);
+ ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__info);
+ ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__warning);
+ ATF_ADD_TEST_CASE(tcs, set_persistency__fail);
+}
diff --git a/utils/memory.cpp b/utils/memory.cpp
new file mode 100644
index 000000000000..ca121f6f4dec
--- /dev/null
+++ b/utils/memory.cpp
@@ -0,0 +1,158 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/memory.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#if defined(HAVE_SYS_TYPES_H)
+# include <sys/types.h>
+#endif
+#if defined(HAVE_SYS_PARAM_H)
+# include <sys/param.h>
+#endif
+#if defined(HAVE_SYS_SYSCTL_H)
+# include <sys/sysctl.h>
+#endif
+}
+
+#include <cerrno>
+#include <cstddef>
+#include <cstring>
+#include <stdexcept>
+
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/units.hpp"
+#include "utils/sanity.hpp"
+
+namespace units = utils::units;
+
+
+namespace {
+
+
+/// Name of the method to query the available memory as detected by configure.
+static const char* query_type = MEMORY_QUERY_TYPE;
+
+
+/// Value of query_type when we do not know how to query the memory.
+static const char* query_type_unknown = "unknown";
+
+
+/// Value of query_type when we have to use sysctlbyname(3).
+static const char* query_type_sysctlbyname = "sysctlbyname";
+
+
+/// Name of the sysctl MIB with the physical memory as detected by configure.
+///
+/// This should only be used if memory_query_type is 'sysctl'.
+static const char* query_sysctl_mib = MEMORY_QUERY_SYSCTL_MIB;
+
+
+#if !defined(HAVE_SYSCTLBYNAME)
+/// Stub for sysctlbyname(3) for systems that don't have it.
+///
+/// The whole purpose of this fake function is to allow the caller code to be
+/// compiled on any machine regardless of the presence of sysctlbyname(3). This
+/// will prevent the code from breaking when it is compiled on a machine without
+/// this function. It also prevents "unused variable" warnings in the caller
+/// code.
+///
+/// \return Nothing; this always crashes.
+static int
+sysctlbyname(const char* /* name */,
+ void* /* oldp */,
+ std::size_t* /* oldlenp */,
+ const void* /* newp */,
+ std::size_t /* newlen */)
+{
+ UNREACHABLE;
+}
+#endif
+
+
+} // anonymous namespace
+
+
+/// Gets the value of an integral sysctl MIB.
+///
+/// \pre The system supports the sysctlbyname(3) function.
+///
+/// \param mib The name of the sysctl MIB to query.
+///
+/// \return The value of the MIB, if found.
+///
+/// \throw std::runtime_error If the sysctlbyname(3) call fails. This might be
+/// a bit drastic. If it turns out that this causes problems, we could just
+/// change the code to log the error instead of raising an exception.
+static int64_t
+query_sysctl(const char* mib)
+{
+ // This must be explicitly initialized to 0. If the sysctl query returned a
+ // value smaller in size than value_length, we would get garbage otherwise.
+ int64_t value = 0;
+ std::size_t value_length = sizeof(value);
+ if (::sysctlbyname(mib, &value, &value_length, NULL, 0) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to get sysctl(%s) value: %s") %
+ mib % std::strerror(original_errno));
+ }
+ return value;
+}
+
+
+/// Queries the total amount of physical memory.
+///
+/// The real query is run only once and the result is cached. Further calls to
+/// this function will always return the same value.
+///
+/// \return The amount of physical memory, in bytes. If the code does not know
+/// how to query the memory, this logs a warning and returns 0.
+units::bytes
+utils::physical_memory(void)
+{
+ static int64_t amount = -1;
+ if (amount == -1) {
+ if (std::strcmp(query_type, query_type_unknown) == 0) {
+ LW("Don't know how to query the physical memory");
+ amount = 0;
+ } else if (std::strcmp(query_type, query_type_sysctlbyname) == 0) {
+ amount = query_sysctl(query_sysctl_mib);
+ } else
+ UNREACHABLE_MSG("Unimplemented memory query type");
+ LI(F("Physical memory as returned by query type '%s': %s") %
+ query_type % amount);
+ }
+ POST(amount > -1);
+ return units::bytes(amount);
+}
diff --git a/utils/memory.hpp b/utils/memory.hpp
new file mode 100644
index 000000000000..5a956a82005a
--- /dev/null
+++ b/utils/memory.hpp
@@ -0,0 +1,45 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/memory.hpp
+/// Utilities to query details of the system memory.
+
+#if !defined(UTILS_MEMORY_HPP)
+#define UTILS_MEMORY_HPP
+
+#include "utils/units_fwd.hpp"
+
+namespace utils {
+
+
+units::bytes physical_memory(void);
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_MEMORY_HPP)
diff --git a/utils/memory_test.cpp b/utils/memory_test.cpp
new file mode 100644
index 000000000000..66750fbe9c6c
--- /dev/null
+++ b/utils/memory_test.cpp
@@ -0,0 +1,63 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+#include "utils/memory.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/units.hpp"
+
+namespace units = utils::units;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(physical_memory);
+ATF_TEST_CASE_BODY(physical_memory)
+{
+ const units::bytes memory = utils::physical_memory();
+
+ if (std::strcmp(MEMORY_QUERY_TYPE, "unknown") == 0) {
+ ATF_REQUIRE(memory == 0);
+ } else if (std::strcmp(MEMORY_QUERY_TYPE, "sysctlbyname") == 0) {
+ ATF_REQUIRE(memory > 0);
+ ATF_REQUIRE(memory < 100 * units::TB); // Large enough for now...
+ } else {
+ fail("Unimplemented memory query type");
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, physical_memory);
+}
diff --git a/utils/noncopyable.hpp b/utils/noncopyable.hpp
new file mode 100644
index 000000000000..6a0ad6bf713a
--- /dev/null
+++ b/utils/noncopyable.hpp
@@ -0,0 +1,75 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/noncopyable.hpp
+/// Provides the utils::noncopyable class.
+///
+/// The class is provided as a separate module on its own to minimize
+/// header-inclusion side-effects.
+
+#if !defined(UTILS_NONCOPYABLE_HPP)
+#define UTILS_NONCOPYABLE_HPP
+
+
+namespace utils {
+
+
+/// Forbids copying a class at compile-time.
+///
+/// Inheriting from this class delivers a private copy constructor and an
+/// assignment operator that effectively forbid copying the class during
+/// compilation.
+///
+/// Always use private inheritance.
+class noncopyable {
+ /// Data placeholder.
+ ///
+ /// The class cannot be empty; otherwise we get ABI-stability warnings
+ /// during the build, which will break it due to strict checking.
+ int _noncopyable_dummy;
+
+ /// Private copy constructor to deny copying of subclasses.
+ noncopyable(const noncopyable&);
+
+ /// Private assignment constructor to deny copying of subclasses.
+ ///
+ /// \return A reference to the object.
+ noncopyable& operator=(const noncopyable&);
+
+protected:
+ // Explicitly needed to provide some non-private functions. Otherwise
+ // we also get some warnings during the build.
+ noncopyable(void) {}
+ ~noncopyable(void) {}
+};
+
+
+} // namespace utils
+
+
+#endif // !defined(UTILS_NONCOPYABLE_HPP)
diff --git a/utils/optional.hpp b/utils/optional.hpp
new file mode 100644
index 000000000000..a4557bff5dc8
--- /dev/null
+++ b/utils/optional.hpp
@@ -0,0 +1,90 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/optional.hpp
+/// Provides the utils::optional class.
+///
+/// The class is provided as a separate module on its own to minimize
+/// header-inclusion side-effects.
+
+#if !defined(UTILS_OPTIONAL_HPP)
+#define UTILS_OPTIONAL_HPP
+
+#include "utils/optional_fwd.hpp"
+
+#include <ostream>
+
+namespace utils {
+
+
+/// Holds a data value or none.
+///
+/// This class allows users to represent values that may be uninitialized.
+/// Instead of having to keep separate variables to track whether a variable is
+/// supposed to have a value or not, this class allows multiplexing the
+/// behaviors.
+///
+/// This class is a simplified version of Boost.Optional.
+template< class T >
+class optional {
+ /// Internal representation of the optional data value.
+ T* _data;
+
+public:
+ optional(void);
+ optional(utils::detail::none_t);
+ optional(const optional< T >&);
+ explicit optional(const T&);
+ ~optional(void);
+
+ optional& operator=(utils::detail::none_t);
+ optional& operator=(const T&);
+ optional& operator=(const optional< T >&);
+
+ bool operator==(const optional< T >&) const;
+ bool operator!=(const optional< T >&) const;
+
+ operator bool(void) const;
+
+ const T& get(void) const;
+ const T& get_default(const T&) const;
+ T& get(void);
+};
+
+
+template< class T >
+std::ostream& operator<<(std::ostream&, const optional< T >&);
+
+
+template< class T >
+optional< T > make_optional(const T&);
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_OPTIONAL_HPP)
diff --git a/utils/optional.ipp b/utils/optional.ipp
new file mode 100644
index 000000000000..3e2f3f878f2a
--- /dev/null
+++ b/utils/optional.ipp
@@ -0,0 +1,252 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_OPTIONAL_IPP)
+#define UTILS_OPTIONAL_IPP
+
+#include <cstddef>
+
+#include "utils/defs.hpp"
+#include "utils/optional.hpp"
+#include "utils/sanity.hpp"
+
+
+/// Initializes an optional object to the none value.
+template< class T >
+utils::optional< T >::optional(void) :
+ _data(NULL)
+{
+}
+
+
+/// Explicitly initializes an optional object to the none value.
+template< class T >
+utils::optional< T >::optional(utils::detail::none_t /* none */) :
+ _data(NULL)
+{
+}
+
+
+/// Initializes an optional object to a non-none value.
+///
+/// \param data The initial value for the object.
+template< class T >
+utils::optional< T >::optional(const T& data) :
+ _data(new T(data))
+{
+}
+
+
+/// Copy constructor.
+///
+/// \param other The optional object to copy from.
+template< class T >
+utils::optional< T >::optional(const optional< T >& other) :
+ _data(other._data == NULL ? NULL : new T(*(other._data)))
+{
+}
+
+
+/// Destructor.
+template< class T >
+utils::optional< T >::~optional(void)
+{
+ if (_data != NULL)
+ delete _data;
+ _data = NULL; // Prevent accidental reuse.
+}
+
+
+/// Explicitly assigns an optional object to the none value.
+///
+/// \return A reference to this.
+template< class T >
+utils::optional< T >&
+utils::optional< T >::operator=(utils::detail::none_t /* none */)
+{
+ if (_data != NULL)
+ delete _data;
+ _data = NULL;
+ return *this;
+}
+
+
+/// Assigns a new value to the optional object.
+///
+/// \param data The initial value for the object.
+///
+/// \return A reference to this.
+template< class T >
+utils::optional< T >&
+utils::optional< T >::operator=(const T& data)
+{
+ T* new_data = new T(data);
+ if (_data != NULL)
+ delete _data;
+ _data = new_data;
+ return *this;
+}
+
+
+/// Copies an optional value.
+///
+/// \param other The optional object to copy from.
+///
+/// \return A reference to this.
+template< class T >
+utils::optional< T >&
+utils::optional< T >::operator=(const optional< T >& other)
+{
+ T* new_data = other._data == NULL ? NULL : new T(*(other._data));
+ if (_data != NULL)
+ delete _data;
+ _data = new_data;
+ return *this;
+}
+
+
+/// Equality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are equal; false otherwise.
+template< class T >
+bool
+utils::optional< T >::operator==(const optional< T >& other) const
+{
+ if (_data == NULL && other._data == NULL) {
+ return true;
+ } else if (_data == NULL || other._data == NULL) {
+ return false;
+ } else {
+ INV(_data != NULL && other._data != NULL);
+ return *_data == *other._data;
+ }
+}
+
+
+/// Inequality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are different; false otherwise.
+template< class T >
+bool
+utils::optional< T >::operator!=(const optional< T >& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Gets the value hold by the optional object.
+///
+/// \pre The optional object must not be none.
+///
+/// \return A reference to the data.
+template< class T >
+const T&
+utils::optional< T >::get(void) const
+{
+ PRE(_data != NULL);
+ return *_data;
+}
+
+
+/// Gets the value of this object with a default fallback.
+///
+/// \param default_value The value to return if this object holds no value.
+///
+/// \return A reference to the data in the optional object, or the reference
+/// passed in as a parameter.
+template< class T >
+const T&
+utils::optional< T >::get_default(const T& default_value) const
+{
+ if (_data != NULL)
+ return *_data;
+ else
+ return default_value;
+}
+
+
+/// Tests whether the optional object contains data or not.
+///
+/// \return True if the object is not none; false otherwise.
+template< class T >
+utils::optional< T >::operator bool(void) const
+{
+ return _data != NULL;
+}
+
+
+/// Tests whether the optional object contains data or not.
+///
+/// \return True if the object is not none; false otherwise.
+template< class T >
+T&
+utils::optional< T >::get(void)
+{
+ PRE(_data != NULL);
+ return *_data;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< class T >
+std::ostream& utils::operator<<(std::ostream& output,
+ const optional< T >& object)
+{
+ if (!object) {
+ output << "none";
+ } else {
+ output << object.get();
+ }
+ return output;
+}
+
+
+/// Helper function to instantiate optional objects.
+///
+/// \param value The value for the optional object. Shouldn't be none, as
+/// optional objects can be constructed from none right away.
+///
+/// \return A new optional object.
+template< class T >
+utils::optional< T >
+utils::make_optional(const T& value)
+{
+ return optional< T >(value);
+}
+
+
+#endif // !defined(UTILS_OPTIONAL_IPP)
diff --git a/utils/optional_fwd.hpp b/utils/optional_fwd.hpp
new file mode 100644
index 000000000000..931dbbfe88da
--- /dev/null
+++ b/utils/optional_fwd.hpp
@@ -0,0 +1,61 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/optional_fwd.hpp
+/// Forward declarations for utils/optional.hpp
+
+#if !defined(UTILS_OPTIONAL_FWD_HPP)
+#define UTILS_OPTIONAL_FWD_HPP
+
+namespace utils {
+
+
+namespace detail {
+
+
+/// Internal type-safe representation for the none type.
+struct none_t {};
+
+
+} // namespace detail
+
+
+/// The none value.
+///
+/// This has internal linkage so it is OK to define it in the header file.
+/// However, pointers to none from different translation units will be
+/// different. Just don't do that.
+const detail::none_t none = {};
+
+
+template< class > class optional;
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_OPTIONAL_FWD_HPP)
diff --git a/utils/optional_test.cpp b/utils/optional_test.cpp
new file mode 100644
index 000000000000..debd8949852e
--- /dev/null
+++ b/utils/optional_test.cpp
@@ -0,0 +1,285 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/optional.ipp"
+
+#include <iostream>
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Fake class to capture calls to the new and delete operators.
+class test_alloc {
+public:
+ /// Value to disambiguate objects after construction.
+ int value;
+
+ /// Balance of alive instances of this class in dynamic memory.
+ static size_t instances;
+
+ /// Constructs a new optional object.
+ ///
+ /// \param value_ The value to store in this object for disambiguation.
+ test_alloc(int value_) : value(value_)
+ {
+ }
+
+ /// Allocates a new object and records its existence.
+ ///
+ /// \param size The amount of memory to allocate.
+ ///
+ /// \return A pointer to the allocated memory.
+ ///
+ /// \throw std::bad_alloc If the memory allocation fails.
+ void*
+ operator new(size_t size)
+ {
+ instances++;
+ std::cout << "test_alloc::operator new called\n";
+ return ::operator new(size);
+ }
+
+ /// Deallocates an existing object and unrecords its existence.
+ ///
+ /// \param mem The pointer to the memory to deallocate.
+ void
+ operator delete(void* mem)
+ {
+ instances--;
+ std::cout << "test_alloc::operator delete called\n";
+ ::operator delete(mem);
+ }
+};
+
+
+size_t test_alloc::instances = 0;
+
+
+/// Constructs and returns an optional object.
+///
+/// This is used by tests to validate that returning an object from within a
+/// function works (i.e. the necessary constructors are available).
+///
+/// \tparam Type The type of the object included in the optional wrapper.
+/// \param value The value to put inside the optional wrapper.
+///
+/// \return The constructed optional object.
+template< typename Type >
+optional< Type >
+return_optional(const Type& value)
+{
+ return optional< Type >(value);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ctors__native_type);
+ATF_TEST_CASE_BODY(ctors__native_type)
+{
+ const optional< int > no_args;
+ ATF_REQUIRE(!no_args);
+
+ const optional< int > with_none(none);
+ ATF_REQUIRE(!with_none);
+
+ const optional< int > with_arg(3);
+ ATF_REQUIRE(with_arg);
+ ATF_REQUIRE_EQ(3, with_arg.get());
+
+ const optional< int > copy_none(with_none);
+ ATF_REQUIRE(!copy_none);
+
+ const optional< int > copy_arg(with_arg);
+ ATF_REQUIRE(copy_arg);
+ ATF_REQUIRE_EQ(3, copy_arg.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ctors__complex_type);
+ATF_TEST_CASE_BODY(ctors__complex_type)
+{
+ const optional< std::string > no_args;
+ ATF_REQUIRE(!no_args);
+
+ const optional< std::string > with_none(none);
+ ATF_REQUIRE(!with_none);
+
+ const optional< std::string > with_arg("foo");
+ ATF_REQUIRE(with_arg);
+ ATF_REQUIRE_EQ("foo", with_arg.get());
+
+ const optional< std::string > copy_none(with_none);
+ ATF_REQUIRE(!copy_none);
+
+ const optional< std::string > copy_arg(with_arg);
+ ATF_REQUIRE(copy_arg);
+ ATF_REQUIRE_EQ("foo", copy_arg.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(assign);
+ATF_TEST_CASE_BODY(assign)
+{
+ optional< int > from_default;
+ from_default = optional< int >();
+ ATF_REQUIRE(!from_default);
+
+ optional< int > from_none(3);
+ from_none = none;
+ ATF_REQUIRE(!from_none);
+
+ optional< int > from_int;
+ from_int = 6;
+ ATF_REQUIRE_EQ(6, from_int.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(return);
+ATF_TEST_CASE_BODY(return)
+{
+ optional< long > from_return(return_optional< long >(123));
+ ATF_REQUIRE(from_return);
+ ATF_REQUIRE_EQ(123, from_return.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(memory);
+ATF_TEST_CASE_BODY(memory)
+{
+ ATF_REQUIRE_EQ(0, test_alloc::instances);
+ {
+ optional< test_alloc > optional1(test_alloc(3));
+ ATF_REQUIRE_EQ(1, test_alloc::instances);
+ ATF_REQUIRE_EQ(3, optional1.get().value);
+
+ {
+ optional< test_alloc > optional2(optional1);
+ ATF_REQUIRE_EQ(2, test_alloc::instances);
+ ATF_REQUIRE_EQ(3, optional2.get().value);
+
+ optional2 = 5;
+ ATF_REQUIRE_EQ(2, test_alloc::instances);
+ ATF_REQUIRE_EQ(5, optional2.get().value);
+ ATF_REQUIRE_EQ(3, optional1.get().value);
+ }
+ ATF_REQUIRE_EQ(1, test_alloc::instances);
+ ATF_REQUIRE_EQ(3, optional1.get().value);
+ }
+ ATF_REQUIRE_EQ(0, test_alloc::instances);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_default);
+ATF_TEST_CASE_BODY(get_default)
+{
+ const std::string def_value = "hello";
+ optional< std::string > optional;
+ ATF_REQUIRE(&def_value == &optional.get_default(def_value));
+ optional = "bye";
+ ATF_REQUIRE_EQ("bye", optional.get_default(def_value));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(make_optional);
+ATF_TEST_CASE_BODY(make_optional)
+{
+ optional< int > opt = utils::make_optional(576);
+ ATF_REQUIRE(opt);
+ ATF_REQUIRE_EQ(576, opt.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne);
+ATF_TEST_CASE_BODY(operators_eq_and_ne)
+{
+ optional< int > opt1, opt2;
+
+ opt1 = none; opt2 = none;
+ ATF_REQUIRE( opt1 == opt2);
+ ATF_REQUIRE(!(opt1 != opt2));
+
+ opt1 = utils::make_optional(5); opt2 = none;
+ ATF_REQUIRE(!(opt1 == opt2));
+ ATF_REQUIRE( opt1 != opt2);
+
+ opt1 = none; opt2 = utils::make_optional(5);
+ ATF_REQUIRE(!(opt1 == opt2));
+ ATF_REQUIRE( opt1 != opt2);
+
+ opt1 = utils::make_optional(5); opt2 = utils::make_optional(5);
+ ATF_REQUIRE( opt1 == opt2);
+ ATF_REQUIRE(!(opt1 != opt2));
+
+ opt1 = utils::make_optional(6); opt2 = utils::make_optional(5);
+ ATF_REQUIRE(!(opt1 == opt2));
+ ATF_REQUIRE( opt1 != opt2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(output);
+ATF_TEST_CASE_BODY(output)
+{
+ {
+ std::ostringstream str;
+ str << optional< int >(none);
+ ATF_REQUIRE_EQ("none", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << optional< int >(5);
+ ATF_REQUIRE_EQ("5", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << optional< std::string >("this is a text");
+ ATF_REQUIRE_EQ("this is a text", str.str());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, ctors__native_type);
+ ATF_ADD_TEST_CASE(tcs, ctors__complex_type);
+ ATF_ADD_TEST_CASE(tcs, assign);
+ ATF_ADD_TEST_CASE(tcs, return);
+ ATF_ADD_TEST_CASE(tcs, memory);
+ ATF_ADD_TEST_CASE(tcs, get_default);
+ ATF_ADD_TEST_CASE(tcs, make_optional);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne);
+ ATF_ADD_TEST_CASE(tcs, output);
+}
diff --git a/utils/passwd.cpp b/utils/passwd.cpp
new file mode 100644
index 000000000000..32a16bb4d462
--- /dev/null
+++ b/utils/passwd.cpp
@@ -0,0 +1,194 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/passwd.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <unistd.h>
+}
+
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+
+namespace passwd_ns = utils::passwd;
+
+
+namespace {
+
+
+/// If defined, replaces the value returned by current_user().
+static utils::optional< passwd_ns::user > fake_current_user;
+
+
+/// If not empty, defines the current set of mock users.
+static std::vector< passwd_ns::user > mock_users;
+
+
+/// Formats a user for logging purposes.
+///
+/// \param user The user to format.
+///
+/// \return The user as a string.
+static std::string
+format_user(const passwd_ns::user& user)
+{
+ return F("name=%s, uid=%s, gid=%s") % user.name % user.uid % user.gid;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new user.
+///
+/// \param name_ The name of the user.
+/// \param uid_ The user identifier.
+/// \param gid_ The login group identifier.
+passwd_ns::user::user(const std::string& name_, const unsigned int uid_,
+ const unsigned int gid_) :
+ name(name_),
+ uid(uid_),
+ gid(gid_)
+{
+}
+
+
+/// Checks if the user has superpowers or not.
+///
+/// \return True if the user is root, false otherwise.
+bool
+passwd_ns::user::is_root(void) const
+{
+ return uid == 0;
+}
+
+
+/// Gets the current user.
+///
+/// \return The current user.
+passwd_ns::user
+passwd_ns::current_user(void)
+{
+ if (fake_current_user) {
+ const user u = fake_current_user.get();
+ LD(F("Current user is fake: %s") % format_user(u));
+ return u;
+ } else {
+ const user u = find_user_by_uid(::getuid());
+ LD(F("Current user is: %s") % format_user(u));
+ return u;
+ }
+}
+
+
+/// Gets information about a user by its name.
+///
+/// \param name The name of the user to query.
+///
+/// \return The information about the user.
+///
+/// \throw std::runtime_error If the user does not exist.
+passwd_ns::user
+passwd_ns::find_user_by_name(const std::string& name)
+{
+ if (mock_users.empty()) {
+ const struct ::passwd* pw = ::getpwnam(name.c_str());
+ if (pw == NULL)
+ throw std::runtime_error(F("Failed to get information about the "
+ "user '%s'") % name);
+ INV(pw->pw_name == name);
+ return user(pw->pw_name, pw->pw_uid, pw->pw_gid);
+ } else {
+ for (std::vector< user >::const_iterator iter = mock_users.begin();
+ iter != mock_users.end(); iter++) {
+ if ((*iter).name == name)
+ return *iter;
+ }
+ throw std::runtime_error(F("Failed to get information about the "
+ "user '%s'") % name);
+ }
+}
+
+
+/// Gets information about a user by its identifier.
+///
+/// \param uid The identifier of the user to query.
+///
+/// \return The information about the user.
+///
+/// \throw std::runtime_error If the user does not exist.
+passwd_ns::user
+passwd_ns::find_user_by_uid(const unsigned int uid)
+{
+ if (mock_users.empty()) {
+ const struct ::passwd* pw = ::getpwuid(uid);
+ if (pw == NULL)
+ throw std::runtime_error(F("Failed to get information about the "
+ "user with UID %s") % uid);
+ INV(pw->pw_uid == uid);
+ return user(pw->pw_name, pw->pw_uid, pw->pw_gid);
+ } else {
+ for (std::vector< user >::const_iterator iter = mock_users.begin();
+ iter != mock_users.end(); iter++) {
+ if ((*iter).uid == uid)
+ return *iter;
+ }
+ throw std::runtime_error(F("Failed to get information about the "
+ "user with UID %s") % uid);
+ }
+}
+
+
+/// Overrides the current user for testing purposes.
+///
+/// This DOES NOT change the current privileges!
+///
+/// \param new_current_user The new current user.
+void
+passwd_ns::set_current_user_for_testing(const user& new_current_user)
+{
+ fake_current_user = new_current_user;
+}
+
+
+/// Overrides the current set of users for testing purposes.
+///
+/// \param users The new users set. Cannot be empty.
+void
+passwd_ns::set_mock_users_for_testing(const std::vector< user >& users)
+{
+ PRE(!users.empty());
+ mock_users = users;
+}
diff --git a/utils/passwd.hpp b/utils/passwd.hpp
new file mode 100644
index 000000000000..e0b17c547080
--- /dev/null
+++ b/utils/passwd.hpp
@@ -0,0 +1,72 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/passwd.hpp
+/// Querying and manipulation of users and groups.
+
+#if !defined(UTILS_PASSWD_HPP)
+#define UTILS_PASSWD_HPP
+
+#include "utils/passwd_fwd.hpp"
+
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace passwd {
+
+
+/// Represents a system user.
+class user {
+public:
+ /// The name of the user.
+ std::string name;
+
+ /// The system-wide identifier of the user.
+ unsigned int uid;
+
+ /// The login group identifier for the user.
+ unsigned int gid;
+
+ user(const std::string&, const unsigned int, const unsigned int);
+
+ bool is_root(void) const;
+};
+
+
+user current_user(void);
+user find_user_by_name(const std::string&);
+user find_user_by_uid(const unsigned int);
+void set_current_user_for_testing(const user&);
+void set_mock_users_for_testing(const std::vector< user >&);
+
+
+} // namespace passwd
+} // namespace utils
+
+#endif // !defined(UTILS_PASSWD_HPP)
diff --git a/utils/passwd_fwd.hpp b/utils/passwd_fwd.hpp
new file mode 100644
index 000000000000..bedbd34c8af8
--- /dev/null
+++ b/utils/passwd_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/passwd_fwd.hpp
+/// Forward declarations for utils/passwd.hpp
+
+#if !defined(UTILS_PASSWD_FWD_HPP)
+#define UTILS_PASSWD_FWD_HPP
+
+namespace utils {
+namespace passwd {
+
+
+class user;
+
+
+} // namespace passwd
+} // namespace utils
+
+#endif // !defined(UTILS_PASSWD_FWD_HPP)
diff --git a/utils/passwd_test.cpp b/utils/passwd_test.cpp
new file mode 100644
index 000000000000..720ecb32e5fe
--- /dev/null
+++ b/utils/passwd_test.cpp
@@ -0,0 +1,179 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/passwd.hpp"
+
+extern "C" {
+#include <sys/wait.h>
+
+#include <pwd.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+namespace passwd_ns = utils::passwd;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(user__public_fields);
+ATF_TEST_CASE_BODY(user__public_fields)
+{
+ const passwd_ns::user user("the-name", 1, 2);
+ ATF_REQUIRE_EQ("the-name", user.name);
+ ATF_REQUIRE_EQ(1, user.uid);
+ ATF_REQUIRE_EQ(2, user.gid);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(user__is_root__true);
+ATF_TEST_CASE_BODY(user__is_root__true)
+{
+ const passwd_ns::user user("i-am-root", 0, 10);
+ ATF_REQUIRE(user.is_root());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(user__is_root__false);
+ATF_TEST_CASE_BODY(user__is_root__false)
+{
+ const passwd_ns::user user("i-am-not-root", 123, 10);
+ ATF_REQUIRE(!user.is_root());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(current_user);
+ATF_TEST_CASE_BODY(current_user)
+{
+ const passwd_ns::user user = passwd_ns::current_user();
+ ATF_REQUIRE_EQ(::getuid(), user.uid);
+ ATF_REQUIRE_EQ(::getgid(), user.gid);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(current_user__fake);
+ATF_TEST_CASE_BODY(current_user__fake)
+{
+ const passwd_ns::user new_user("someone-else", ::getuid() + 1, 0);
+ passwd_ns::set_current_user_for_testing(new_user);
+
+ const passwd_ns::user user = passwd_ns::current_user();
+ ATF_REQUIRE(::getuid() != user.uid);
+ ATF_REQUIRE_EQ(new_user.uid, user.uid);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_user_by_name__ok);
+ATF_TEST_CASE_BODY(find_user_by_name__ok)
+{
+ const struct ::passwd* pw = ::getpwuid(::getuid());
+ ATF_REQUIRE(pw != NULL);
+
+ const passwd_ns::user user = passwd_ns::find_user_by_name(pw->pw_name);
+ ATF_REQUIRE_EQ(::getuid(), user.uid);
+ ATF_REQUIRE_EQ(::getgid(), user.gid);
+ ATF_REQUIRE_EQ(pw->pw_name, user.name);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_user_by_name__fail);
+ATF_TEST_CASE_BODY(find_user_by_name__fail)
+{
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Failed.*user 'i-do-not-exist'",
+ passwd_ns::find_user_by_name("i-do-not-exist"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_user_by_name__fake);
+ATF_TEST_CASE_BODY(find_user_by_name__fake)
+{
+ std::vector< passwd_ns::user > users;
+ users.push_back(passwd_ns::user("myself2", 20, 40));
+ users.push_back(passwd_ns::user("myself1", 10, 15));
+ users.push_back(passwd_ns::user("myself3", 30, 60));
+ passwd_ns::set_mock_users_for_testing(users);
+
+ const passwd_ns::user user = passwd_ns::find_user_by_name("myself1");
+ ATF_REQUIRE_EQ(10, user.uid);
+ ATF_REQUIRE_EQ(15, user.gid);
+ ATF_REQUIRE_EQ("myself1", user.name);
+
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Failed.*user 'root'",
+ passwd_ns::find_user_by_name("root"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_user_by_uid__ok);
+ATF_TEST_CASE_BODY(find_user_by_uid__ok)
+{
+ const passwd_ns::user user = passwd_ns::find_user_by_uid(::getuid());
+ ATF_REQUIRE_EQ(::getuid(), user.uid);
+ ATF_REQUIRE_EQ(::getgid(), user.gid);
+
+ const struct ::passwd* pw = ::getpwuid(::getuid());
+ ATF_REQUIRE(pw != NULL);
+ ATF_REQUIRE_EQ(pw->pw_name, user.name);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_user_by_uid__fake);
+ATF_TEST_CASE_BODY(find_user_by_uid__fake)
+{
+ std::vector< passwd_ns::user > users;
+ users.push_back(passwd_ns::user("myself2", 20, 40));
+ users.push_back(passwd_ns::user("myself1", 10, 15));
+ users.push_back(passwd_ns::user("myself3", 30, 60));
+ passwd_ns::set_mock_users_for_testing(users);
+
+ const passwd_ns::user user = passwd_ns::find_user_by_uid(10);
+ ATF_REQUIRE_EQ(10, user.uid);
+ ATF_REQUIRE_EQ(15, user.gid);
+ ATF_REQUIRE_EQ("myself1", user.name);
+
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Failed.*user.*UID 0",
+ passwd_ns::find_user_by_uid(0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, user__public_fields);
+ ATF_ADD_TEST_CASE(tcs, user__is_root__true);
+ ATF_ADD_TEST_CASE(tcs, user__is_root__false);
+
+ ATF_ADD_TEST_CASE(tcs, current_user);
+ ATF_ADD_TEST_CASE(tcs, current_user__fake);
+
+ ATF_ADD_TEST_CASE(tcs, find_user_by_name__ok);
+ ATF_ADD_TEST_CASE(tcs, find_user_by_name__fail);
+ ATF_ADD_TEST_CASE(tcs, find_user_by_name__fake);
+ ATF_ADD_TEST_CASE(tcs, find_user_by_uid__ok);
+ ATF_ADD_TEST_CASE(tcs, find_user_by_uid__fake);
+}
diff --git a/utils/process/.gitignore b/utils/process/.gitignore
new file mode 100644
index 000000000000..fb3291b39e0c
--- /dev/null
+++ b/utils/process/.gitignore
@@ -0,0 +1 @@
+helpers
diff --git a/utils/process/Kyuafile b/utils/process/Kyuafile
new file mode 100644
index 000000000000..92e62cfac6fc
--- /dev/null
+++ b/utils/process/Kyuafile
@@ -0,0 +1,13 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="child_test"}
+atf_test_program{name="deadline_killer_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="executor_test"}
+atf_test_program{name="fdstream_test"}
+atf_test_program{name="isolation_test"}
+atf_test_program{name="operations_test"}
+atf_test_program{name="status_test"}
+atf_test_program{name="systembuf_test"}
diff --git a/utils/process/Makefile.am.inc b/utils/process/Makefile.am.inc
new file mode 100644
index 000000000000..3cff02e7e455
--- /dev/null
+++ b/utils/process/Makefile.am.inc
@@ -0,0 +1,113 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/process/child.cpp
+libutils_a_SOURCES += utils/process/child.hpp
+libutils_a_SOURCES += utils/process/child.ipp
+libutils_a_SOURCES += utils/process/child_fwd.hpp
+libutils_a_SOURCES += utils/process/deadline_killer.cpp
+libutils_a_SOURCES += utils/process/deadline_killer.hpp
+libutils_a_SOURCES += utils/process/deadline_killer_fwd.hpp
+libutils_a_SOURCES += utils/process/exceptions.cpp
+libutils_a_SOURCES += utils/process/exceptions.hpp
+libutils_a_SOURCES += utils/process/executor.cpp
+libutils_a_SOURCES += utils/process/executor.hpp
+libutils_a_SOURCES += utils/process/executor.ipp
+libutils_a_SOURCES += utils/process/executor_fwd.hpp
+libutils_a_SOURCES += utils/process/fdstream.cpp
+libutils_a_SOURCES += utils/process/fdstream.hpp
+libutils_a_SOURCES += utils/process/fdstream_fwd.hpp
+libutils_a_SOURCES += utils/process/isolation.cpp
+libutils_a_SOURCES += utils/process/isolation.hpp
+libutils_a_SOURCES += utils/process/operations.cpp
+libutils_a_SOURCES += utils/process/operations.hpp
+libutils_a_SOURCES += utils/process/operations_fwd.hpp
+libutils_a_SOURCES += utils/process/status.cpp
+libutils_a_SOURCES += utils/process/status.hpp
+libutils_a_SOURCES += utils/process/status_fwd.hpp
+libutils_a_SOURCES += utils/process/system.cpp
+libutils_a_SOURCES += utils/process/system.hpp
+libutils_a_SOURCES += utils/process/systembuf.cpp
+libutils_a_SOURCES += utils/process/systembuf.hpp
+libutils_a_SOURCES += utils/process/systembuf_fwd.hpp
+
+if WITH_ATF
+tests_utils_processdir = $(pkgtestsdir)/utils/process
+
+tests_utils_process_DATA = utils/process/Kyuafile
+EXTRA_DIST += $(tests_utils_process_DATA)
+
+tests_utils_process_PROGRAMS = utils/process/child_test
+utils_process_child_test_SOURCES = utils/process/child_test.cpp
+utils_process_child_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_child_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/deadline_killer_test
+utils_process_deadline_killer_test_SOURCES = \
+ utils/process/deadline_killer_test.cpp
+utils_process_deadline_killer_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_deadline_killer_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/exceptions_test
+utils_process_exceptions_test_SOURCES = utils/process/exceptions_test.cpp
+utils_process_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/executor_test
+utils_process_executor_test_SOURCES = utils/process/executor_test.cpp
+utils_process_executor_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_executor_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/fdstream_test
+utils_process_fdstream_test_SOURCES = utils/process/fdstream_test.cpp
+utils_process_fdstream_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_fdstream_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/isolation_test
+utils_process_isolation_test_SOURCES = utils/process/isolation_test.cpp
+utils_process_isolation_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_isolation_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/helpers
+utils_process_helpers_SOURCES = utils/process/helpers.cpp
+
+tests_utils_process_PROGRAMS += utils/process/operations_test
+utils_process_operations_test_SOURCES = utils/process/operations_test.cpp
+utils_process_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/status_test
+utils_process_status_test_SOURCES = utils/process/status_test.cpp
+utils_process_status_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_status_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_process_PROGRAMS += utils/process/systembuf_test
+utils_process_systembuf_test_SOURCES = utils/process/systembuf_test.cpp
+utils_process_systembuf_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_process_systembuf_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/process/child.cpp b/utils/process/child.cpp
new file mode 100644
index 000000000000..fef09ccaad3b
--- /dev/null
+++ b/utils/process/child.cpp
@@ -0,0 +1,385 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/child.ipp"
+
+extern "C" {
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <iostream>
+#include <memory>
+
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/process/exceptions.hpp"
+#include "utils/process/fdstream.hpp"
+#include "utils/process/operations.hpp"
+#include "utils/process/system.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/interrupts.hpp"
+
+
+namespace utils {
+namespace process {
+
+
+/// Private implementation fields for child objects.
+struct child::impl : utils::noncopyable {
+ /// The process identifier.
+ pid_t _pid;
+
+ /// The input stream for the process' stdout and stderr. May be NULL.
+ std::auto_ptr< process::ifdstream > _output;
+
+ /// Initializes private implementation data.
+ ///
+ /// \param pid The process identifier.
+ /// \param output The input stream. Grabs ownership of the pointer.
+ impl(const pid_t pid, process::ifdstream* output) :
+ _pid(pid), _output(output) {}
+};
+
+
+} // namespace process
+} // namespace utils
+
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+/// Exception-based version of dup(2).
+///
+/// \param old_fd The file descriptor to duplicate.
+/// \param new_fd The file descriptor to use as the duplicate. This is
+/// closed if it was open before the copy happens.
+///
+/// \throw process::system_error If the call to dup2(2) fails.
+static void
+safe_dup(const int old_fd, const int new_fd)
+{
+ if (process::detail::syscall_dup2(old_fd, new_fd) == -1) {
+ const int original_errno = errno;
+ throw process::system_error(F("dup2(%s, %s) failed") % old_fd % new_fd,
+ original_errno);
+ }
+}
+
+
+/// Exception-based version of open(2) to open (or create) a file for append.
+///
+/// \param filename The file to open in append mode.
+///
+/// \return The file descriptor for the opened or created file.
+///
+/// \throw process::system_error If the call to open(2) fails.
+static int
+open_for_append(const fs::path& filename)
+{
+ const int fd = process::detail::syscall_open(
+ filename.c_str(), O_CREAT | O_WRONLY | O_APPEND,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd == -1) {
+ const int original_errno = errno;
+ throw process::system_error(F("Failed to create %s because open(2) "
+ "failed") % filename, original_errno);
+ }
+ return fd;
+}
+
+
+/// Logs the execution of another program.
+///
+/// \param program The binary to execute.
+/// \param args The arguments to pass to the binary, without the program name.
+static void
+log_exec(const fs::path& program, const process::args_vector& args)
+{
+ std::string plain_command = program.str();
+ for (process::args_vector::const_iterator iter = args.begin();
+ iter != args.end(); ++iter)
+ plain_command += F(" %s") % *iter;
+ LD(F("Executing %s") % plain_command);
+}
+
+
+} // anonymous namespace
+
+
+/// Prints out a fatal error and aborts.
+void
+utils::process::detail::report_error_and_abort(void)
+{
+ std::cerr << "Caught unknown exception\n";
+ std::abort();
+}
+
+
+/// Prints out a fatal error and aborts.
+///
+/// \param error The error to display.
+void
+utils::process::detail::report_error_and_abort(const std::runtime_error& error)
+{
+ std::cerr << "Caught runtime_error: " << error.what() << '\n';
+ std::abort();
+}
+
+
+/// Creates a new child.
+///
+/// \param implptr A dynamically-allocated impl object with the contents of the
+/// new child.
+process::child::child(impl *implptr) :
+ _pimpl(implptr)
+{
+}
+
+
+/// Destructor for child.
+process::child::~child(void)
+{
+}
+
+
+/// Helper function for fork().
+///
+/// Please note: if you update this function to change the return type or to
+/// raise different errors, do not forget to update fork() accordingly.
+///
+/// \return In the case of the parent, a new child object returned as a
+/// dynamically-allocated object because children classes are unique and thus
+/// noncopyable. In the case of the child, a NULL pointer.
+///
+/// \throw process::system_error If the calls to pipe(2) or fork(2) fail.
+std::auto_ptr< process::child >
+process::child::fork_capture_aux(void)
+{
+ std::cout.flush();
+ std::cerr.flush();
+
+ int fds[2];
+ if (detail::syscall_pipe(fds) == -1)
+ throw process::system_error("pipe(2) failed", errno);
+
+ std::auto_ptr< signals::interrupts_inhibiter > inhibiter(
+ new signals::interrupts_inhibiter);
+ pid_t pid = detail::syscall_fork();
+ if (pid == -1) {
+ inhibiter.reset(NULL); // Unblock signals.
+ ::close(fds[0]);
+ ::close(fds[1]);
+ throw process::system_error("fork(2) failed", errno);
+ } else if (pid == 0) {
+ inhibiter.reset(NULL); // Unblock signals.
+ ::setsid();
+
+ try {
+ ::close(fds[0]);
+ safe_dup(fds[1], STDOUT_FILENO);
+ safe_dup(fds[1], STDERR_FILENO);
+ ::close(fds[1]);
+ } catch (const system_error& e) {
+ std::cerr << F("Failed to set up subprocess: %s\n") % e.what();
+ std::abort();
+ }
+ return std::auto_ptr< process::child >(NULL);
+ } else {
+ ::close(fds[1]);
+ LD(F("Spawned process %s: stdout and stderr inherited") % pid);
+ signals::add_pid_to_kill(pid);
+ inhibiter.reset(NULL); // Unblock signals.
+ return std::auto_ptr< process::child >(
+ new process::child(new impl(pid, new process::ifdstream(fds[0]))));
+ }
+}
+
+
+/// Helper function for fork().
+///
+/// Please note: if you update this function to change the return type or to
+/// raise different errors, do not forget to update fork() accordingly.
+///
+/// \param stdout_file The name of the file in which to store the stdout.
+/// If this has the magic value /dev/stdout, then the parent's stdout is
+/// reused without applying any redirection.
+/// \param stderr_file The name of the file in which to store the stderr.
+/// If this has the magic value /dev/stderr, then the parent's stderr is
+/// reused without applying any redirection.
+///
+/// \return In the case of the parent, a new child object returned as a
+/// dynamically-allocated object because children classes are unique and thus
+/// noncopyable. In the case of the child, a NULL pointer.
+///
+/// \throw process::system_error If the call to fork(2) fails.
+std::auto_ptr< process::child >
+process::child::fork_files_aux(const fs::path& stdout_file,
+ const fs::path& stderr_file)
+{
+ std::cout.flush();
+ std::cerr.flush();
+
+ std::auto_ptr< signals::interrupts_inhibiter > inhibiter(
+ new signals::interrupts_inhibiter);
+ pid_t pid = detail::syscall_fork();
+ if (pid == -1) {
+ inhibiter.reset(NULL); // Unblock signals.
+ throw process::system_error("fork(2) failed", errno);
+ } else if (pid == 0) {
+ inhibiter.reset(NULL); // Unblock signals.
+ ::setsid();
+
+ try {
+ if (stdout_file != fs::path("/dev/stdout")) {
+ const int stdout_fd = open_for_append(stdout_file);
+ safe_dup(stdout_fd, STDOUT_FILENO);
+ ::close(stdout_fd);
+ }
+ if (stderr_file != fs::path("/dev/stderr")) {
+ const int stderr_fd = open_for_append(stderr_file);
+ safe_dup(stderr_fd, STDERR_FILENO);
+ ::close(stderr_fd);
+ }
+ } catch (const system_error& e) {
+ std::cerr << F("Failed to set up subprocess: %s\n") % e.what();
+ std::abort();
+ }
+ return std::auto_ptr< process::child >(NULL);
+ } else {
+ LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file %
+ stderr_file);
+ signals::add_pid_to_kill(pid);
+ inhibiter.reset(NULL); // Unblock signals.
+ return std::auto_ptr< process::child >(
+ new process::child(new impl(pid, NULL)));
+ }
+}
+
+
+/// Spawns a new binary and multiplexes and captures its stdout and stderr.
+///
+/// If the subprocess cannot be completely set up for any reason, it attempts to
+/// dump an error message to its stderr channel and it then calls std::abort().
+///
+/// \param program The binary to execute.
+/// \param args The arguments to pass to the binary, without the program name.
+///
+/// \return A new child object, returned as a dynamically-allocated object
+/// because children classes are unique and thus noncopyable.
+///
+/// \throw process::system_error If the process cannot be spawned due to a
+/// system call error.
+std::auto_ptr< process::child >
+process::child::spawn_capture(const fs::path& program, const args_vector& args)
+{
+ std::auto_ptr< child > child = fork_capture_aux();
+ if (child.get() == NULL)
+ exec(program, args);
+ log_exec(program, args);
+ return child;
+}
+
+
+/// Spawns a new binary and redirects its stdout and stderr to files.
+///
+/// If the subprocess cannot be completely set up for any reason, it attempts to
+/// dump an error message to its stderr channel and it then calls std::abort().
+///
+/// \param program The binary to execute.
+/// \param args The arguments to pass to the binary, without the program name.
+/// \param stdout_file The name of the file in which to store the stdout.
+/// \param stderr_file The name of the file in which to store the stderr.
+///
+/// \return A new child object, returned as a dynamically-allocated object
+/// because children classes are unique and thus noncopyable.
+///
+/// \throw process::system_error If the process cannot be spawned due to a
+/// system call error.
+std::auto_ptr< process::child >
+process::child::spawn_files(const fs::path& program,
+ const args_vector& args,
+ const fs::path& stdout_file,
+ const fs::path& stderr_file)
+{
+ std::auto_ptr< child > child = fork_files_aux(stdout_file, stderr_file);
+ if (child.get() == NULL)
+ exec(program, args);
+ log_exec(program, args);
+ return child;
+}
+
+
+/// Returns the process identifier of this child.
+///
+/// \return A process identifier.
+int
+process::child::pid(void) const
+{
+ return _pimpl->_pid;
+}
+
+
+/// Gets the input stream corresponding to the stdout and stderr of the child.
+///
+/// \pre The child must have been started by fork_capture().
+///
+/// \return A reference to the input stream connected to the output of the test
+/// case.
+std::istream&
+process::child::output(void)
+{
+ PRE(_pimpl->_output.get() != NULL);
+ return *_pimpl->_output;
+}
+
+
+/// Blocks to wait for completion.
+///
+/// \return The termination status of the child process.
+///
+/// \throw process::system_error If the call to waitpid(2) fails.
+process::status
+process::child::wait(void)
+{
+ return process::wait(_pimpl->_pid);
+}
diff --git a/utils/process/child.hpp b/utils/process/child.hpp
new file mode 100644
index 000000000000..2c9450f6500a
--- /dev/null
+++ b/utils/process/child.hpp
@@ -0,0 +1,113 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/child.hpp
+/// Spawning and manipulation of children processes.
+///
+/// The child module provides a set of functions to spawn subprocesses with
+/// different settings, and the corresponding set of classes to interact with
+/// said subprocesses. The interfaces to fork subprocesses are very simplified
+/// and only provide the minimum functionality required by the rest of the
+/// project.
+///
+/// Be aware that the semantics of the fork and wait methods exposed by this
+/// module are slightly different from that of the native calls. Any process
+/// spawned by fork here will be isolated in its own session; once any of
+/// such children processes is awaited for, its whole process group will be
+/// terminated. This is the semantics we want in the above layers to ensure
+/// that test programs (and, for that matter, external utilities) do not leak
+/// subprocesses on the system.
+
+#if !defined(UTILS_PROCESS_CHILD_HPP)
+#define UTILS_PROCESS_CHILD_HPP
+
+#include "utils/process/child_fwd.hpp"
+
+#include <istream>
+#include <memory>
+#include <stdexcept>
+
+#include "utils/defs.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/process/operations_fwd.hpp"
+#include "utils/process/status_fwd.hpp"
+
+namespace utils {
+namespace process {
+
+
+namespace detail {
+
+void report_error_and_abort(void) UTILS_NORETURN;
+void report_error_and_abort(const std::runtime_error&) UTILS_NORETURN;
+
+
+} // namespace detail
+
+
+/// Child process spawner and controller.
+class child : noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ static std::auto_ptr< child > fork_capture_aux(void);
+
+ static std::auto_ptr< child > fork_files_aux(const fs::path&,
+ const fs::path&);
+
+ explicit child(impl *);
+
+public:
+ ~child(void);
+
+ template< typename Hook >
+ static std::auto_ptr< child > fork_capture(Hook);
+ std::istream& output(void);
+
+ template< typename Hook >
+ static std::auto_ptr< child > fork_files(Hook, const fs::path&,
+ const fs::path&);
+
+ static std::auto_ptr< child > spawn_capture(
+ const fs::path&, const args_vector&);
+ static std::auto_ptr< child > spawn_files(
+ const fs::path&, const args_vector&, const fs::path&, const fs::path&);
+
+ int pid(void) const;
+
+ status wait(void);
+};
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_CHILD_HPP)
diff --git a/utils/process/child.ipp b/utils/process/child.ipp
new file mode 100644
index 000000000000..aa90373652fd
--- /dev/null
+++ b/utils/process/child.ipp
@@ -0,0 +1,110 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_PROCESS_CHILD_IPP)
+#define UTILS_PROCESS_CHILD_IPP
+
+#include <cstdlib>
+
+#include "utils/process/child.hpp"
+
+namespace utils {
+namespace process {
+
+
+/// Spawns a new subprocess and redirects its stdout and stderr to files.
+///
+/// If the subprocess cannot be completely set up for any reason, it attempts to
+/// dump an error message to its stderr channel and it then calls std::abort().
+///
+/// \param hook The function to execute in the subprocess. Must not return.
+/// \param stdout_file The name of the file in which to store the stdout.
+/// \param stderr_file The name of the file in which to store the stderr.
+///
+/// \return A new child object, returned as a dynamically-allocated object
+/// because children classes are unique and thus noncopyable.
+///
+/// \throw process::system_error If the process cannot be spawned due to a
+/// system call error.
+template< typename Hook >
+std::auto_ptr< child >
+child::fork_files(Hook hook, const fs::path& stdout_file,
+ const fs::path& stderr_file)
+{
+ std::auto_ptr< child > child = fork_files_aux(stdout_file, stderr_file);
+ if (child.get() == NULL) {
+ try {
+ hook();
+ std::abort();
+ } catch (const std::runtime_error& e) {
+ detail::report_error_and_abort(e);
+ } catch (...) {
+ detail::report_error_and_abort();
+ }
+ }
+
+ return child;
+}
+
+
+/// Spawns a new subprocess and multiplexes and captures its stdout and stderr.
+///
+/// If the subprocess cannot be completely set up for any reason, it attempts to
+/// dump an error message to its stderr channel and it then calls std::abort().
+///
+/// \param hook The function to execute in the subprocess. Must not return.
+///
+/// \return A new child object, returned as a dynamically-allocated object
+/// because children classes are unique and thus noncopyable.
+///
+/// \throw process::system_error If the process cannot be spawned due to a
+/// system call error.
+template< typename Hook >
+std::auto_ptr< child >
+child::fork_capture(Hook hook)
+{
+ std::auto_ptr< child > child = fork_capture_aux();
+ if (child.get() == NULL) {
+ try {
+ hook();
+ std::abort();
+ } catch (const std::runtime_error& e) {
+ detail::report_error_and_abort(e);
+ } catch (...) {
+ detail::report_error_and_abort();
+ }
+ }
+
+ return child;
+}
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_CHILD_IPP)
diff --git a/utils/process/child_fwd.hpp b/utils/process/child_fwd.hpp
new file mode 100644
index 000000000000..4d4caa17d58c
--- /dev/null
+++ b/utils/process/child_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/child_fwd.hpp
+/// Forward declarations for utils/process/child.hpp
+
+#if !defined(UTILS_PROCESS_CHILD_FWD_HPP)
+#define UTILS_PROCESS_CHILD_FWD_HPP
+
+namespace utils {
+namespace process {
+
+
+class child;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_CHILD_FWD_HPP)
diff --git a/utils/process/child_test.cpp b/utils/process/child_test.cpp
new file mode 100644
index 000000000000..69de9991ae13
--- /dev/null
+++ b/utils/process/child_test.cpp
@@ -0,0 +1,846 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/child.ipp"
+
+extern "C" {
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdarg>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/process/exceptions.hpp"
+#include "utils/process/status.hpp"
+#include "utils/process/system.hpp"
+#include "utils/sanity.hpp"
+#include "utils/test_utils.ipp"
+
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// Checks if the current subprocess is in its own session.
+static void
+child_check_own_session(void)
+{
+ std::exit((::getsid(::getpid()) == ::getpid()) ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+/// Body for a process that prints a simple message and exits.
+///
+/// \tparam ExitStatus The exit status for the subprocess.
+/// \tparam Message A single character that will be prepended to the printed
+/// messages. This would ideally be a string, but we cannot templatize a
+/// function with an object nor a pointer.
+template< int ExitStatus, char Message >
+static void
+child_simple_function(void)
+{
+ std::cout << "To stdout: " << Message << "\n";
+ std::cerr << "To stderr: " << Message << "\n";
+ std::exit(ExitStatus);
+}
+
+
+/// Functor for the body of a process that prints a simple message and exits.
+class child_simple_functor {
+ /// The exit status that the subprocess will yield.
+ int _exitstatus;
+
+ /// The message to print on stdout and stderr.
+ std::string _message;
+
+public:
+ /// Constructs a new functor.
+ ///
+ /// \param exitstatus The exit status that the subprocess will yield.
+ /// \param message The message to print on stdout and stderr.
+ child_simple_functor(const int exitstatus, const std::string& message) :
+ _exitstatus(exitstatus),
+ _message(message)
+ {
+ }
+
+ /// Body for the subprocess.
+ void
+ operator()(void)
+ {
+ std::cout << "To stdout: " << _message << "\n";
+ std::cerr << "To stderr: " << _message << "\n";
+ std::exit(_exitstatus);
+ }
+};
+
+
+/// Body for a process that prints many messages to stdout and exits.
+///
+/// The goal of this body is to validate that any buffering performed on the
+/// parent process to read the output of the subprocess works correctly.
+static void
+child_printer_function(void)
+{
+ for (std::size_t i = 0; i < 100; i++)
+ std::cout << "This is a message to stdout, sequence " << i << "\n";
+ std::cout.flush();
+ std::cerr << "Exiting\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// Functor for the body of a process that runs child_printer_function.
+class child_printer_functor {
+public:
+ /// Body for the subprocess.
+ void
+ operator()(void)
+ {
+ child_printer_function();
+ }
+};
+
+
+/// Body for a child process that throws an exception.
+static void
+child_throw_exception(void)
+{
+ throw std::runtime_error("A loose exception");
+}
+
+
+/// Body for a child process that creates a pidfile.
+static void
+child_write_pid(void)
+{
+ std::ofstream output("pidfile");
+ output << ::getpid() << "\n";
+ output.close();
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// A child process that returns.
+///
+/// The fork() wrappers are supposed to capture this condition and terminate the
+/// child before the code returns to the fork() call point.
+static void
+child_return(void)
+{
+}
+
+
+/// A child process that raises an exception.
+///
+/// The fork() wrappers are supposed to capture this condition and terminate the
+/// child before the code returns to the fork() call point.
+///
+/// \tparam Type The type of the exception to raise.
+/// \tparam Value The value passed to the constructor of the exception type. In
+/// general, this only makes sense if Type is a primitive type so that, in
+/// the end, the code becomes "throw int(123)".
+///
+/// \throw Type An exception of the provided type.
+template< class Type, Type Value >
+void
+child_raise_exception(void)
+{
+ throw Type(Value);
+}
+
+
+/// Calculates the path to the test helpers binary.
+///
+/// \param tc A pointer to the caller test case, needed to extract the value of
+/// the "srcdir" property.
+///
+/// \return The path to the helpers binary.
+static fs::path
+get_helpers(const atf::tests::tc* tc)
+{
+ return fs::path(tc->get_config_var("srcdir")) / "helpers";
+}
+
+
+/// Mock fork(2) that just returns an error.
+///
+/// \tparam Errno The value to set as the errno of the failed call.
+///
+/// \return Always -1.
+template< int Errno >
+static pid_t
+fork_fail(void) throw()
+{
+ errno = Errno;
+ return -1;
+}
+
+
+/// Mock open(2) that fails if the 'raise-error' file is opened.
+///
+/// \tparam Errno The value to set as the errno if the known failure triggers.
+/// \param path The path to the file to be opened.
+/// \param flags The open flags.
+/// \param ... The file mode creation, if flags contains O_CREAT.
+///
+/// \return The opened file handle or -1 on error.
+template< int Errno >
+static int
+open_fail(const char* path, const int flags, ...) throw()
+{
+ if (std::strcmp(path, "raise-error") == 0) {
+ errno = Errno;
+ return -1;
+ } else {
+ va_list ap;
+ va_start(ap, flags);
+ const int mode = va_arg(ap, int);
+ va_end(ap);
+ return ::open(path, flags, mode);
+ }
+}
+
+
+/// Mock pipe(2) that just returns an error.
+///
+/// \tparam Errno The value to set as the errno of the failed call.
+///
+/// \return Always -1.
+template< int Errno >
+static pid_t
+pipe_fail(int* /* fildes */) throw()
+{
+ errno = Errno;
+ return -1;
+}
+
+
+/// Helper for child tests to validate inheritance of stdout/stderr.
+///
+/// This function ensures that passing one of /dev/stdout or /dev/stderr to
+/// the child__fork_files fork method does the right thing. The idea is that we
+/// call fork with the given parameters and then make our child redirect one of
+/// its file descriptors to a specific file without going through the process
+/// library. We then validate if this redirection worked and got the expected
+/// output.
+///
+/// \param fork_stdout The path to pass to the fork call as the stdout file.
+/// \param fork_stderr The path to pass to the fork call as the stderr file.
+/// \param child_file The file to explicitly in the subchild.
+/// \param child_fd The file descriptor to which to attach child_file.
+static void
+do_inherit_test(const char* fork_stdout, const char* fork_stderr,
+ const char* child_file, const int child_fd)
+{
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ logging::set_inmemory();
+
+ const int fd = ::open(child_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd != child_fd) {
+ if (::dup2(fd, child_fd) == -1)
+ std::abort();
+ ::close(fd);
+ }
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 123, 'Z' >,
+ fs::path(fork_stdout), fs::path(fork_stderr));
+ const process::status status = child->wait();
+ if (!status.exited() || status.exitstatus() != 123)
+ std::abort();
+ std::exit(EXIT_SUCCESS);
+ } else {
+ int status;
+ ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+ ATF_REQUIRE(atf::utils::grep_file("stdout: Z", "stdout.txt"));
+ ATF_REQUIRE(atf::utils::grep_file("stderr: Z", "stderr.txt"));
+ }
+}
+
+
+/// Performs a "child__fork_capture__ok_*" test.
+///
+/// This test basically ensures that the child__fork_capture class spawns a
+/// process whose output is captured in an input stream.
+///
+/// \tparam Hook The type of the fork hook to use.
+/// \param hook The hook to the fork call.
+template< class Hook >
+static void
+child__fork_capture__ok(Hook hook)
+{
+ std::cout << "This unflushed message should not propagate to the child";
+ std::cerr << "This unflushed message should not propagate to the child";
+ std::auto_ptr< process::child > child = process::child::fork_capture(hook);
+ std::cout.flush();
+ std::cerr.flush();
+
+ std::istream& output = child->output();
+ for (std::size_t i = 0; i < 100; i++) {
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ((F("This is a message to stdout, "
+ "sequence %s") % i).str(), line);
+ }
+
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ("Exiting", line);
+
+ process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_function);
+ATF_TEST_CASE_BODY(child__fork_capture__ok_function)
+{
+ child__fork_capture__ok(child_printer_function);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_functor);
+ATF_TEST_CASE_BODY(child__fork_capture__ok_functor)
+{
+ child__fork_capture__ok(child_printer_functor());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__catch_exceptions);
+ATF_TEST_CASE_BODY(child__fork_capture__catch_exceptions)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_throw_exception);
+
+ std::string message;
+ std::istream& output = child->output();
+ ATF_REQUIRE(std::getline(output, message).good());
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+
+ ATF_REQUIRE_MATCH("Caught.*A loose exception", message);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__new_session);
+ATF_TEST_CASE_BODY(child__fork_capture__new_session)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_check_own_session);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__pipe_fail);
+ATF_TEST_CASE_BODY(child__fork_capture__pipe_fail)
+{
+ process::detail::syscall_pipe = pipe_fail< 23 >;
+ try {
+ process::child::fork_capture(child_simple_function< 1, 'A' >);
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("pipe.*failed", e.what()));
+ ATF_REQUIRE_EQ(23, e.original_errno());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_exit);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_exit)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_return);
+ if (::getpid() != parent_pid) {
+ // If we enter this clause, it is because the hook returned.
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_unwind);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_unwind)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+ try {
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_raise_exception< int, 123 >);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+ } catch (const int i) {
+ // If we enter this clause, it is because an exception leaked from the
+ // hook.
+ INV(parent_pid != ::getpid());
+ INV(i == 123);
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_fail);
+ATF_TEST_CASE_BODY(child__fork_capture__fork_fail)
+{
+ process::detail::syscall_fork = fork_fail< 89 >;
+ try {
+ process::child::fork_capture(child_simple_function< 1, 'A' >);
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what()));
+ ATF_REQUIRE_EQ(89, e.original_errno());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_function);
+ATF_TEST_CASE_BODY(child__fork_files__ok_function)
+{
+ const fs::path file1("file1.txt");
+ const fs::path file2("file2.txt");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 15, 'Z' >, file1, file2);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(15, status.exitstatus());
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stdout: Z$", file1.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stdout: Z$", file2.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stderr: Z$", file2.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stderr: Z$", file1.str()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_functor);
+ATF_TEST_CASE_BODY(child__fork_files__ok_functor)
+{
+ const fs::path filea("fileA.txt");
+ const fs::path fileb("fileB.txt");
+
+ atf::utils::create_file(filea.str(), "Initial stdout\n");
+ atf::utils::create_file(fileb.str(), "Initial stderr\n");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_functor(16, "a functor"), filea, fileb);
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(16, status.exitstatus());
+
+ ATF_REQUIRE( atf::utils::grep_file("^Initial stdout$", filea.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^Initial stdout$", fileb.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stdout: a functor$", filea.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stdout: a functor$", fileb.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^Initial stderr$", fileb.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^Initial stderr$", filea.str()));
+
+ ATF_REQUIRE( atf::utils::grep_file("^To stderr: a functor$", fileb.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("^To stderr: a functor$", filea.str()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__catch_exceptions);
+ATF_TEST_CASE_BODY(child__fork_files__catch_exceptions)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_throw_exception,
+ fs::path("unused.out"), fs::path("stderr"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+
+ ATF_REQUIRE(atf::utils::grep_file("Caught.*A loose exception", "stderr"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__new_session);
+ATF_TEST_CASE_BODY(child__fork_files__new_session)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_check_own_session,
+ fs::path("unused.out"), fs::path("unused.err"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stdout);
+ATF_TEST_CASE_BODY(child__fork_files__inherit_stdout)
+{
+ do_inherit_test("/dev/stdout", "stderr.txt", "stdout.txt", STDOUT_FILENO);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stderr);
+ATF_TEST_CASE_BODY(child__fork_files__inherit_stderr)
+{
+ do_inherit_test("stdout.txt", "/dev/stderr", "stderr.txt", STDERR_FILENO);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_exit);
+ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_exit)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_return, fs::path("out"), fs::path("err"));
+ if (::getpid() != parent_pid) {
+ // If we enter this clause, it is because the hook returned.
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_unwind);
+ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_unwind)
+{
+ const pid_t parent_pid = ::getpid();
+ atf::utils::create_file("to-not-be-deleted", "");
+ try {
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_raise_exception< int, 123 >, fs::path("out"),
+ fs::path("err"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted")));
+ } catch (const int i) {
+ // If we enter this clause, it is because an exception leaked from the
+ // hook.
+ INV(parent_pid != ::getpid());
+ INV(i == 123);
+ ::unlink("to-not-be-deleted");
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_fail);
+ATF_TEST_CASE_BODY(child__fork_files__fork_fail)
+{
+ process::detail::syscall_fork = fork_fail< 1234 >;
+ try {
+ process::child::fork_files(child_simple_function< 1, 'A' >,
+ fs::path("a.txt"), fs::path("b.txt"));
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what()));
+ ATF_REQUIRE_EQ(1234, e.original_errno());
+ }
+ ATF_REQUIRE(!fs::exists(fs::path("a.txt")));
+ ATF_REQUIRE(!fs::exists(fs::path("b.txt")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stdout_fail);
+ATF_TEST_CASE_BODY(child__fork_files__create_stdout_fail)
+{
+ process::detail::syscall_open = open_fail< ENOENT >;
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 1, 'A' >, fs::path("raise-error"),
+ fs::path("created"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(!fs::exists(fs::path("raise-error")));
+ ATF_REQUIRE(!fs::exists(fs::path("created")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stderr_fail);
+ATF_TEST_CASE_BODY(child__fork_files__create_stderr_fail)
+{
+ process::detail::syscall_open = open_fail< ENOENT >;
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_simple_function< 1, 'A' >, fs::path("created"),
+ fs::path("raise-error"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(fs::exists(fs::path("created")));
+ ATF_REQUIRE(!fs::exists(fs::path("raise-error")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__absolute_path);
+ATF_TEST_CASE_BODY(child__spawn__absolute_path)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("12");
+
+ const fs::path program = get_helpers(this);
+ INV(program.is_absolute());
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ program, args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(12, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__relative_path);
+ATF_TEST_CASE_BODY(child__spawn__relative_path)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("13");
+
+ ATF_REQUIRE(::mkdir("root", 0755) != -1);
+ ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "root/helpers") != -1);
+
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ fs::path("root/helpers"), args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(13, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__basename_only);
+ATF_TEST_CASE_BODY(child__spawn__basename_only)
+{
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("14");
+
+ ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "helpers") != -1);
+
+ std::auto_ptr< process::child > child = process::child::spawn_files(
+ fs::path("helpers"), args, fs::path("out"), fs::path("err"));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(14, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_path);
+ATF_TEST_CASE_BODY(child__spawn__no_path)
+{
+ logging::set_inmemory();
+
+ std::vector< std::string > args;
+ args.push_back("return-code");
+ args.push_back("14");
+
+ const fs::path helpers = get_helpers(this);
+ utils::setenv("PATH", helpers.branch_path().c_str());
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ fs::path(helpers.leaf_name()), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_MATCH("Failed to execute", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_args);
+ATF_TEST_CASE_BODY(child__spawn__no_args)
+{
+ std::vector< std::string > args;
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ get_helpers(this), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("Must provide a helper name", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__some_args);
+ATF_TEST_CASE_BODY(child__spawn__some_args)
+{
+ std::vector< std::string > args;
+ args.push_back("print-args");
+ args.push_back("foo");
+ args.push_back(" bar baz ");
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ get_helpers(this), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("argv[0] = " + get_helpers(this).str(), line);
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ ATF_REQUIRE_EQ("argv[1] = print-args", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[2] = foo", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[3] = bar baz ", line);
+ ATF_REQUIRE(std::getline(child->output(), line));
+ ATF_REQUIRE_EQ("argv[4] = NULL", line);
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__missing_program);
+ATF_TEST_CASE_BODY(child__spawn__missing_program)
+{
+ std::vector< std::string > args;
+ std::auto_ptr< process::child > child = process::child::spawn_capture(
+ fs::path("a/b/c"), args);
+
+ std::string line;
+ ATF_REQUIRE(std::getline(child->output(), line).good());
+ const std::string exp = "Failed to execute a/b/c: ";
+ ATF_REQUIRE_EQ(exp, line.substr(0, exp.length()));
+ ATF_REQUIRE(!std::getline(child->output(), line));
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(child__pid);
+ATF_TEST_CASE_BODY(child__pid)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_write_pid);
+
+ const int pid = child->pid();
+
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+
+ std::ifstream input("pidfile");
+ ATF_REQUIRE(input);
+ int read_pid;
+ input >> read_pid;
+ input.close();
+
+ ATF_REQUIRE_EQ(read_pid, pid);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ utils::avoid_coredump_on_crash();
+
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_function);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_functor);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__catch_exceptions);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__new_session);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__pipe_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_exit);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_unwind);
+ ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_fail);
+
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_function);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_functor);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__catch_exceptions);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__new_session);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stdout);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stderr);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_exit);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_unwind);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stdout_fail);
+ ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stderr_fail);
+
+ ATF_ADD_TEST_CASE(tcs, child__spawn__absolute_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__relative_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__basename_only);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__no_path);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__no_args);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__some_args);
+ ATF_ADD_TEST_CASE(tcs, child__spawn__missing_program);
+
+ ATF_ADD_TEST_CASE(tcs, child__pid);
+}
diff --git a/utils/process/deadline_killer.cpp b/utils/process/deadline_killer.cpp
new file mode 100644
index 000000000000..ed733e402f76
--- /dev/null
+++ b/utils/process/deadline_killer.cpp
@@ -0,0 +1,54 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/deadline_killer.hpp"
+
+#include "utils/datetime.hpp"
+#include "utils/process/operations.hpp"
+
+namespace datetime = utils::datetime;
+namespace process = utils::process;
+
+
+/// Constructor.
+///
+/// \param delta Time to the timer activation.
+/// \param pid PID of the process (and process group) to kill.
+process::deadline_killer::deadline_killer(const datetime::delta& delta,
+ const int pid) :
+ signals::timer(delta), _pid(pid)
+{
+}
+
+
+/// Timer activation callback.
+void
+process::deadline_killer::callback(void)
+{
+ process::terminate_group(_pid);
+}
diff --git a/utils/process/deadline_killer.hpp b/utils/process/deadline_killer.hpp
new file mode 100644
index 000000000000..8b337a0f9d8c
--- /dev/null
+++ b/utils/process/deadline_killer.hpp
@@ -0,0 +1,58 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/deadline_killer.hpp
+/// Timer to kill a process on activation.
+
+#if !defined(UTILS_PROCESS_DEADLINE_KILLER_HPP)
+#define UTILS_PROCESS_DEADLINE_KILLER_HPP
+
+#include "utils/process/deadline_killer_fwd.hpp"
+
+#include "utils/signals/timer.hpp"
+
+namespace utils {
+namespace process {
+
+
+/// Timer that forcibly kills a process group on activation.
+class deadline_killer : public utils::signals::timer {
+ /// PID of the process (and process group) to kill.
+ const int _pid;
+
+ void callback(void);
+
+public:
+ deadline_killer(const datetime::delta&, const int);
+};
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_DEADLINE_KILLER_HPP)
diff --git a/utils/process/deadline_killer_fwd.hpp b/utils/process/deadline_killer_fwd.hpp
new file mode 100644
index 000000000000..fca3c5dc57c7
--- /dev/null
+++ b/utils/process/deadline_killer_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/deadline_killer_fwd.hpp
+/// Forward declarations for utils/process/deadline_killer.hpp
+
+#if !defined(UTILS_PROCESS_DEADLINE_KILLER_FWD_HPP)
+#define UTILS_PROCESS_DEADLINE_KILLER_FWD_HPP
+
+namespace utils {
+namespace process {
+
+
+class deadline_killer;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_DEADLINE_KILLER_FWD_HPP)
diff --git a/utils/process/deadline_killer_test.cpp b/utils/process/deadline_killer_test.cpp
new file mode 100644
index 000000000000..06c89660ac31
--- /dev/null
+++ b/utils/process/deadline_killer_test.cpp
@@ -0,0 +1,108 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/deadline_killer.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+
+namespace datetime = utils::datetime;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// Body of a child process that sleeps and then exits.
+///
+/// \tparam Seconds The delay the subprocess has to sleep for.
+template< int Seconds >
+static void
+child_sleep(void)
+{
+ ::sleep(Seconds);
+ std::exit(EXIT_SUCCESS);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(activation);
+ATF_TEST_CASE_BODY(activation)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_sleep< 60 >);
+
+ datetime::timestamp start = datetime::timestamp::now();
+ process::deadline_killer killer(datetime::delta(1, 0), child->pid());
+ const process::status status = child->wait();
+ killer.unprogram();
+ datetime::timestamp end = datetime::timestamp::now();
+
+ ATF_REQUIRE(killer.fired());
+ ATF_REQUIRE(end - start <= datetime::delta(10, 0));
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_activation);
+ATF_TEST_CASE_BODY(no_activation)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_sleep< 1 >);
+
+ datetime::timestamp start = datetime::timestamp::now();
+ process::deadline_killer killer(datetime::delta(60, 0), child->pid());
+ const process::status status = child->wait();
+ killer.unprogram();
+ datetime::timestamp end = datetime::timestamp::now();
+
+ ATF_REQUIRE(!killer.fired());
+ ATF_REQUIRE(end - start <= datetime::delta(10, 0));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, activation);
+ ATF_ADD_TEST_CASE(tcs, no_activation);
+}
diff --git a/utils/process/exceptions.cpp b/utils/process/exceptions.cpp
new file mode 100644
index 000000000000..d7590c330499
--- /dev/null
+++ b/utils/process/exceptions.cpp
@@ -0,0 +1,91 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/exceptions.hpp"
+
+#include <cstring>
+
+#include "utils/format/macros.hpp"
+
+namespace process = utils::process;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+process::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+process::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error based on an errno code.
+///
+/// \param message_ The message describing what caused the error.
+/// \param errno_ The error code.
+process::system_error::system_error(const std::string& message_,
+ const int errno_) :
+ error(F("%s: %s") % message_ % strerror(errno_)),
+ _original_errno(errno_)
+{
+}
+
+
+/// Destructor for the error.
+process::system_error::~system_error(void) throw()
+{
+}
+
+
+/// \return The original errno value.
+int
+process::system_error::original_errno(void) const throw()
+{
+ return _original_errno;
+}
+
+
+/// Constructs a new timeout_error.
+///
+/// \param message_ The message describing what caused the error.
+process::timeout_error::timeout_error(const std::string& message_) :
+ error(message_)
+{
+}
+
+
+/// Destructor for the error.
+process::timeout_error::~timeout_error(void) throw()
+{
+}
diff --git a/utils/process/exceptions.hpp b/utils/process/exceptions.hpp
new file mode 100644
index 000000000000..3bf740459864
--- /dev/null
+++ b/utils/process/exceptions.hpp
@@ -0,0 +1,78 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/exceptions.hpp
+/// Exception types raised by the process module.
+
+#if !defined(UTILS_PROCESS_EXCEPTIONS_HPP)
+#define UTILS_PROCESS_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+namespace utils {
+namespace process {
+
+
+/// Base exceptions for process errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exceptions for errno-based errors.
+///
+/// TODO(jmmv): This code is duplicated in, at least, utils::fs. Figure
+/// out a way to reuse this exception while maintaining the correct inheritance
+/// (i.e. be able to keep it as a child of process::error).
+class system_error : public error {
+ /// Error number describing this libc error condition.
+ int _original_errno;
+
+public:
+ explicit system_error(const std::string&, const int);
+ ~system_error(void) throw();
+
+ int original_errno(void) const throw();
+};
+
+
+/// Denotes that a deadline was exceeded.
+class timeout_error : public error {
+public:
+ explicit timeout_error(const std::string&);
+ ~timeout_error(void) throw();
+};
+
+
+} // namespace process
+} // namespace utils
+
+
+#endif // !defined(UTILS_PROCESS_EXCEPTIONS_HPP)
diff --git a/utils/process/exceptions_test.cpp b/utils/process/exceptions_test.cpp
new file mode 100644
index 000000000000..375b635fc173
--- /dev/null
+++ b/utils/process/exceptions_test.cpp
@@ -0,0 +1,63 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/exceptions.hpp"
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+
+namespace process = utils::process;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const process::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(system_error);
+ATF_TEST_CASE_BODY(system_error)
+{
+ const process::system_error e("Call failed", ENOENT);
+ const std::string expected = F("Call failed: %s") % std::strerror(ENOENT);
+ ATF_REQUIRE_EQ(expected, e.what());
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, system_error);
+}
diff --git a/utils/process/executor.cpp b/utils/process/executor.cpp
new file mode 100644
index 000000000000..dbdf31268f86
--- /dev/null
+++ b/utils/process/executor.cpp
@@ -0,0 +1,869 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/executor.ipp"
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+}
+
+#include <fstream>
+#include <map>
+#include <memory>
+#include <stdexcept>
+
+#include "utils/datetime.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/auto_cleaners.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/logging/operations.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/deadline_killer.hpp"
+#include "utils/process/isolation.hpp"
+#include "utils/process/operations.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/interrupts.hpp"
+#include "utils/signals/timer.hpp"
+
+namespace datetime = utils::datetime;
+namespace executor = utils::process::executor;
+namespace fs = utils::fs;
+namespace logging = utils::logging;
+namespace passwd = utils::passwd;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Template for temporary directories created by the executor.
+static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX";
+
+
+/// Mapping of active subprocess PIDs to their execution data.
+typedef std::map< int, executor::exec_handle > exec_handles_map;
+
+
+} // anonymous namespace
+
+
+/// Basename of the file containing the stdout of the subprocess.
+const char* utils::process::executor::detail::stdout_name = "stdout.txt";
+
+
+/// Basename of the file containing the stderr of the subprocess.
+const char* utils::process::executor::detail::stderr_name = "stderr.txt";
+
+
+/// Basename of the subdirectory in which the subprocess is actually executed.
+///
+/// This is a subdirectory of the "unique work directory" generated for the
+/// subprocess so that our code can create control files on disk and not
+/// get them clobbered by the subprocess's activity.
+const char* utils::process::executor::detail::work_subdir = "work";
+
+
+/// Prepares a subprocess to run a user-provided hook in a controlled manner.
+///
+/// \param unprivileged_user User to switch to if not none.
+/// \param control_directory Path to the subprocess-specific control directory.
+/// \param work_directory Path to the subprocess-specific work directory.
+void
+utils::process::executor::detail::setup_child(
+ const optional< passwd::user > unprivileged_user,
+ const fs::path& control_directory,
+ const fs::path& work_directory)
+{
+ logging::set_inmemory();
+ process::isolate_path(unprivileged_user, control_directory);
+ process::isolate_child(unprivileged_user, work_directory);
+}
+
+
+/// Internal implementation for the exit_handle class.
+struct utils::process::executor::exec_handle::impl : utils::noncopyable {
+ /// PID of the process being run.
+ int pid;
+
+ /// Path to the subprocess-specific work directory.
+ fs::path control_directory;
+
+ /// Path to the subprocess's stdout file.
+ const fs::path stdout_file;
+
+ /// Path to the subprocess's stderr file.
+ const fs::path stderr_file;
+
+ /// Start time.
+ datetime::timestamp start_time;
+
+ /// User the subprocess is running as if different than the current one.
+ const optional< passwd::user > unprivileged_user;
+
+ /// Timer to kill the subprocess on activation.
+ process::deadline_killer timer;
+
+ /// Number of owners of the on-disk state.
+ executor::detail::refcnt_t state_owners;
+
+ /// Constructor.
+ ///
+ /// \param pid_ PID of the forked process.
+ /// \param control_directory_ Path to the subprocess-specific work
+ /// directory.
+ /// \param stdout_file_ Path to the subprocess's stdout file.
+ /// \param stderr_file_ Path to the subprocess's stderr file.
+ /// \param start_time_ Timestamp of when this object was constructed.
+ /// \param timeout Maximum amount of time the subprocess can run for.
+ /// \param unprivileged_user_ User the subprocess is running as if
+ /// different than the current one.
+ /// \param [in,out] state_owners_ Number of owners of the on-disk state.
+ /// For first-time processes, this should be a new counter set to 0;
+ /// for followup processes, this should point to the same counter used
+ /// by the preceding process.
+ impl(const int pid_,
+ const fs::path& control_directory_,
+ const fs::path& stdout_file_,
+ const fs::path& stderr_file_,
+ const datetime::timestamp& start_time_,
+ const datetime::delta& timeout,
+ const optional< passwd::user > unprivileged_user_,
+ executor::detail::refcnt_t state_owners_) :
+ pid(pid_),
+ control_directory(control_directory_),
+ stdout_file(stdout_file_),
+ stderr_file(stderr_file_),
+ start_time(start_time_),
+ unprivileged_user(unprivileged_user_),
+ timer(timeout, pid_),
+ state_owners(state_owners_)
+ {
+ (*state_owners)++;
+ POST(*state_owners > 0);
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed internal implementation.
+executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) :
+ _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+executor::exec_handle::~exec_handle(void)
+{
+}
+
+
+/// Returns the PID of the process being run.
+///
+/// \return A PID.
+int
+executor::exec_handle::pid(void) const
+{
+ return _pimpl->pid;
+}
+
+
+/// Returns the path to the subprocess-specific control directory.
+///
+/// This is where the executor may store control files.
+///
+/// \return The path to a directory that exists until cleanup() is called.
+fs::path
+executor::exec_handle::control_directory(void) const
+{
+ return _pimpl->control_directory;
+}
+
+
+/// Returns the path to the subprocess-specific work directory.
+///
+/// This is guaranteed to be clear of files created by the executor.
+///
+/// \return The path to a directory that exists until cleanup() is called.
+fs::path
+executor::exec_handle::work_directory(void) const
+{
+ return _pimpl->control_directory / detail::work_subdir;
+}
+
+
+/// Returns the path to the subprocess's stdout file.
+///
+/// \return The path to a file that exists until cleanup() is called.
+const fs::path&
+executor::exec_handle::stdout_file(void) const
+{
+ return _pimpl->stdout_file;
+}
+
+
+/// Returns the path to the subprocess's stderr file.
+///
+/// \return The path to a file that exists until cleanup() is called.
+const fs::path&
+executor::exec_handle::stderr_file(void) const
+{
+ return _pimpl->stderr_file;
+}
+
+
+/// Internal implementation for the exit_handle class.
+struct utils::process::executor::exit_handle::impl : utils::noncopyable {
+ /// Original PID of the terminated subprocess.
+ ///
+ /// Note that this PID is no longer valid and cannot be used on system
+ /// tables!
+ const int original_pid;
+
+ /// Termination status of the subprocess, or none if it timed out.
+ const optional< process::status > status;
+
+ /// The user the process ran as, if different than the current one.
+ const optional< passwd::user > unprivileged_user;
+
+ /// Timestamp of when the subprocess was spawned.
+ const datetime::timestamp start_time;
+
+ /// Timestamp of when wait() or wait_any() returned this object.
+ const datetime::timestamp end_time;
+
+ /// Path to the subprocess-specific work directory.
+ const fs::path control_directory;
+
+ /// Path to the subprocess's stdout file.
+ const fs::path stdout_file;
+
+ /// Path to the subprocess's stderr file.
+ const fs::path stderr_file;
+
+ /// Number of owners of the on-disk state.
+ ///
+ /// This will be 1 if this exit_handle is the last holder of the on-disk
+ /// state, in which case cleanup() invocations will wipe the disk state.
+ /// For all other cases, this will hold a higher value.
+ detail::refcnt_t state_owners;
+
+ /// Mutable pointer to the corresponding executor state.
+ ///
+ /// This object references a member of the executor_handle that yielded this
+ /// exit_handle instance. We need this direct access to clean up after
+ /// ourselves when the handle is destroyed.
+ exec_handles_map& all_exec_handles;
+
+ /// Whether the subprocess state has been cleaned yet or not.
+ ///
+ /// Used to keep track of explicit calls to the public cleanup().
+ bool cleaned;
+
+ /// Constructor.
+ ///
+ /// \param original_pid_ Original PID of the terminated subprocess.
+ /// \param status_ Termination status of the subprocess, or none if
+ /// timed out.
+ /// \param unprivileged_user_ The user the process ran as, if different than
+ /// the current one.
+ /// \param start_time_ Timestamp of when the subprocess was spawned.
+ /// \param end_time_ Timestamp of when wait() or wait_any() returned this
+ /// object.
+ /// \param control_directory_ Path to the subprocess-specific work
+ /// directory.
+ /// \param stdout_file_ Path to the subprocess's stdout file.
+ /// \param stderr_file_ Path to the subprocess's stderr file.
+ /// \param [in,out] state_owners_ Number of owners of the on-disk state.
+ /// \param [in,out] all_exec_handles_ Global object keeping track of all
+ /// active executions for an executor. This is a pointer to a member of
+ /// the executor_handle object.
+ impl(const int original_pid_,
+ const optional< process::status > status_,
+ const optional< passwd::user > unprivileged_user_,
+ const datetime::timestamp& start_time_,
+ const datetime::timestamp& end_time_,
+ const fs::path& control_directory_,
+ const fs::path& stdout_file_,
+ const fs::path& stderr_file_,
+ detail::refcnt_t state_owners_,
+ exec_handles_map& all_exec_handles_) :
+ original_pid(original_pid_), status(status_),
+ unprivileged_user(unprivileged_user_),
+ start_time(start_time_), end_time(end_time_),
+ control_directory(control_directory_),
+ stdout_file(stdout_file_), stderr_file(stderr_file_),
+ state_owners(state_owners_),
+ all_exec_handles(all_exec_handles_), cleaned(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ if (!cleaned) {
+ LW(F("Implicitly cleaning up exit_handle for exec_handle %s; "
+ "ignoring errors!") % original_pid);
+ try {
+ cleanup();
+ } catch (const std::runtime_error& error) {
+ LE(F("Subprocess cleanup failed: %s") % error.what());
+ }
+ }
+ }
+
+ /// Cleans up the subprocess on-disk state.
+ ///
+ /// \throw engine::error If the cleanup fails, especially due to the
+ /// inability to remove the work directory.
+ void
+ cleanup(void)
+ {
+ PRE(*state_owners > 0);
+ if (*state_owners == 1) {
+ LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid);
+ fs::rm_r(control_directory);
+ } else {
+ LI(F("Not cleaning up exit_handle for exec_handle %s; "
+ "%s owners left") % original_pid % (*state_owners - 1));
+ }
+ // We must decrease our reference only after we have successfully
+ // cleaned up the control directory. Otherwise, the rm_r call would
+ // throw an exception, which would in turn invoke the implicit cleanup
+ // from the destructor, which would make us crash due to an invalid
+ // reference count.
+ (*state_owners)--;
+ // Marking this object as clean here, even if we did not do actually the
+ // cleaning above, is fine (albeit a bit confusing). Note that "another
+ // owner" refers to a handle for a different PID, so that handle will be
+ // the one issuing the cleanup.
+ all_exec_handles.erase(original_pid);
+ cleaned = true;
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed internal implementation.
+executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) :
+ _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+executor::exit_handle::~exit_handle(void)
+{
+}
+
+
+/// Cleans up the subprocess status.
+///
+/// This function should be called explicitly as it provides the means to
+/// control any exceptions raised during cleanup. Do not rely on the destructor
+/// to clean things up.
+///
+/// \throw engine::error If the cleanup fails, especially due to the inability
+/// to remove the work directory.
+void
+executor::exit_handle::cleanup(void)
+{
+ PRE(!_pimpl->cleaned);
+ _pimpl->cleanup();
+ POST(_pimpl->cleaned);
+}
+
+
+/// Gets the current number of owners of the on-disk data.
+///
+/// \return A shared reference counter. Even though this function is marked as
+/// const, the return value is intentionally mutable because we need to update
+/// reference counts from different but related processes. This is why this
+/// method is not public.
+std::shared_ptr< std::size_t >
+executor::exit_handle::state_owners(void) const
+{
+ return _pimpl->state_owners;
+}
+
+
+/// Returns the original PID corresponding to the terminated subprocess.
+///
+/// \return An exec_handle.
+int
+executor::exit_handle::original_pid(void) const
+{
+ return _pimpl->original_pid;
+}
+
+
+/// Returns the process termination status of the subprocess.
+///
+/// \return A process termination status, or none if the subprocess timed out.
+const optional< process::status >&
+executor::exit_handle::status(void) const
+{
+ return _pimpl->status;
+}
+
+
+/// Returns the user the process ran as if different than the current one.
+///
+/// \return None if the credentials of the process were the same as the current
+/// one, or else a user.
+const optional< passwd::user >&
+executor::exit_handle::unprivileged_user(void) const
+{
+ return _pimpl->unprivileged_user;
+}
+
+
+/// Returns the timestamp of when the subprocess was spawned.
+///
+/// \return A timestamp.
+const datetime::timestamp&
+executor::exit_handle::start_time(void) const
+{
+ return _pimpl->start_time;
+}
+
+
+/// Returns the timestamp of when wait() or wait_any() returned this object.
+///
+/// \return A timestamp.
+const datetime::timestamp&
+executor::exit_handle::end_time(void) const
+{
+ return _pimpl->end_time;
+}
+
+
+/// Returns the path to the subprocess-specific control directory.
+///
+/// This is where the executor may store control files.
+///
+/// \return The path to a directory that exists until cleanup() is called.
+fs::path
+executor::exit_handle::control_directory(void) const
+{
+ return _pimpl->control_directory;
+}
+
+
+/// Returns the path to the subprocess-specific work directory.
+///
+/// This is guaranteed to be clear of files created by the executor.
+///
+/// \return The path to a directory that exists until cleanup() is called.
+fs::path
+executor::exit_handle::work_directory(void) const
+{
+ return _pimpl->control_directory / detail::work_subdir;
+}
+
+
+/// Returns the path to the subprocess's stdout file.
+///
+/// \return The path to a file that exists until cleanup() is called.
+const fs::path&
+executor::exit_handle::stdout_file(void) const
+{
+ return _pimpl->stdout_file;
+}
+
+
+/// Returns the path to the subprocess's stderr file.
+///
+/// \return The path to a file that exists until cleanup() is called.
+const fs::path&
+executor::exit_handle::stderr_file(void) const
+{
+ return _pimpl->stderr_file;
+}
+
+
+/// Internal implementation for the executor_handle.
+///
+/// Because the executor is a singleton, these essentially is a container for
+/// global variables.
+struct utils::process::executor::executor_handle::impl : utils::noncopyable {
+ /// Numeric counter of executed subprocesses.
+ ///
+ /// This is used to generate a unique identifier for each subprocess as an
+ /// easy mechanism to discern their unique work directories.
+ size_t last_subprocess;
+
+ /// Interrupts handler.
+ std::auto_ptr< signals::interrupts_handler > interrupts_handler;
+
+ /// Root work directory for all executed subprocesses.
+ std::auto_ptr< fs::auto_directory > root_work_directory;
+
+ /// Mapping of PIDs to the data required at run time.
+ exec_handles_map all_exec_handles;
+
+ /// Whether the executor state has been cleaned yet or not.
+ ///
+ /// Used to keep track of explicit calls to the public cleanup().
+ bool cleaned;
+
+ /// Constructor.
+ impl(void) :
+ last_subprocess(0),
+ interrupts_handler(new signals::interrupts_handler()),
+ root_work_directory(new fs::auto_directory(
+ fs::auto_directory::mkdtemp_public(work_directory_template))),
+ cleaned(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ if (!cleaned) {
+ LW("Implicitly cleaning up executor; ignoring errors!");
+ try {
+ cleanup();
+ cleaned = true;
+ } catch (const std::runtime_error& error) {
+ LE(F("Executor global cleanup failed: %s") % error.what());
+ }
+ }
+ }
+
+ /// Cleans up the executor state.
+ void
+ cleanup(void)
+ {
+ PRE(!cleaned);
+
+ for (exec_handles_map::const_iterator iter = all_exec_handles.begin();
+ iter != all_exec_handles.end(); ++iter) {
+ const int& pid = (*iter).first;
+ const exec_handle& data = (*iter).second;
+
+ process::terminate_group(pid);
+ int status;
+ if (::waitpid(pid, &status, 0) == -1) {
+ // Should not happen.
+ LW(F("Failed to wait for PID %s") % pid);
+ }
+
+ try {
+ fs::rm_r(data.control_directory());
+ } catch (const fs::error& e) {
+ LE(F("Failed to clean up subprocess work directory %s: %s") %
+ data.control_directory() % e.what());
+ }
+ }
+ all_exec_handles.clear();
+
+ try {
+ // The following only causes the work directory to be deleted, not
+ // any of its contents, so we expect this to always succeed. This
+ // *should* be sufficient because, in the loop above, we have
+ // individually wiped the subdirectories of any still-unclean
+ // subprocesses.
+ root_work_directory->cleanup();
+ } catch (const fs::error& e) {
+ LE(F("Failed to clean up executor work directory %s: %s; this is "
+ "an internal error") % root_work_directory->directory()
+ % e.what());
+ }
+ root_work_directory.reset(NULL);
+
+ interrupts_handler->unprogram();
+ interrupts_handler.reset(NULL);
+ }
+
+ /// Common code to run after any of the wait calls.
+ ///
+ /// \param original_pid The PID of the terminated subprocess.
+ /// \param status The exit status of the terminated subprocess.
+ ///
+ /// \return A pointer to an object describing the waited-for subprocess.
+ executor::exit_handle
+ post_wait(const int original_pid, const process::status& status)
+ {
+ PRE(original_pid == status.dead_pid());
+ LI(F("Waited for subprocess with exec_handle %s") % original_pid);
+
+ process::terminate_group(status.dead_pid());
+
+ const exec_handles_map::iterator iter = all_exec_handles.find(
+ original_pid);
+ exec_handle& data = (*iter).second;
+ data._pimpl->timer.unprogram();
+
+ // It is tempting to assert here (and old code did) that, if the timer
+ // has fired, the process has been forcibly killed by us. This is not
+ // always the case though: for short-lived processes and with very short
+ // timeouts (think 1ms), it is possible for scheduling decisions to
+ // allow the subprocess to finish while at the same time cause the timer
+ // to fire. So we do not assert this any longer and just rely on the
+ // timer expiration to check if the process timed out or not. If the
+ // process did finish but the timer expired... oh well, we do not detect
+ // this correctly but we don't care because this should not really
+ // happen.
+
+ if (!fs::exists(data.stdout_file())) {
+ std::ofstream new_stdout(data.stdout_file().c_str());
+ }
+ if (!fs::exists(data.stderr_file())) {
+ std::ofstream new_stderr(data.stderr_file().c_str());
+ }
+
+ return exit_handle(std::shared_ptr< exit_handle::impl >(
+ new exit_handle::impl(
+ data.pid(),
+ data._pimpl->timer.fired() ?
+ none : utils::make_optional(status),
+ data._pimpl->unprivileged_user,
+ data._pimpl->start_time, datetime::timestamp::now(),
+ data.control_directory(),
+ data.stdout_file(),
+ data.stderr_file(),
+ data._pimpl->state_owners,
+ all_exec_handles)));
+ }
+};
+
+
+/// Constructor.
+executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl())
+{
+}
+
+
+/// Destructor.
+executor::executor_handle::~executor_handle(void)
+{
+}
+
+
+/// Queries the path to the root of the work directory for all subprocesses.
+///
+/// \return A path.
+const fs::path&
+executor::executor_handle::root_work_directory(void) const
+{
+ return _pimpl->root_work_directory->directory();
+}
+
+
+/// Cleans up the executor state.
+///
+/// This function should be called explicitly as it provides the means to
+/// control any exceptions raised during cleanup. Do not rely on the destructor
+/// to clean things up.
+///
+/// \throw engine::error If there are problems cleaning up the executor.
+void
+executor::executor_handle::cleanup(void)
+{
+ PRE(!_pimpl->cleaned);
+ _pimpl->cleanup();
+ _pimpl->cleaned = true;
+}
+
+
+/// Initializes the executor.
+///
+/// \pre This function can only be called if there is no other executor_handle
+/// object alive.
+///
+/// \return A handle to the operations of the executor.
+executor::executor_handle
+executor::setup(void)
+{
+ return executor_handle();
+}
+
+
+/// Pre-helper for the spawn() method.
+///
+/// \return The created control directory for the subprocess.
+fs::path
+executor::executor_handle::spawn_pre(void)
+{
+ signals::check_interrupt();
+
+ ++_pimpl->last_subprocess;
+
+ const fs::path control_directory =
+ _pimpl->root_work_directory->directory() /
+ (F("%s") % _pimpl->last_subprocess);
+ fs::mkdir_p(control_directory / detail::work_subdir, 0755);
+
+ return control_directory;
+}
+
+
+/// Post-helper for the spawn() method.
+///
+/// \param control_directory Control directory as returned by spawn_pre().
+/// \param stdout_file Path to the subprocess' stdout.
+/// \param stderr_file Path to the subprocess' stderr.
+/// \param timeout Maximum amount of time the subprocess can run for.
+/// \param unprivileged_user If not none, user to switch to before execution.
+/// \param child The process created by spawn().
+///
+/// \return The execution handle of the started subprocess.
+executor::exec_handle
+executor::executor_handle::spawn_post(
+ const fs::path& control_directory,
+ const fs::path& stdout_file,
+ const fs::path& stderr_file,
+ const datetime::delta& timeout,
+ const optional< passwd::user > unprivileged_user,
+ std::auto_ptr< process::child > child)
+{
+ const exec_handle handle(std::shared_ptr< exec_handle::impl >(
+ new exec_handle::impl(
+ child->pid(),
+ control_directory,
+ stdout_file,
+ stderr_file,
+ datetime::timestamp::now(),
+ timeout,
+ unprivileged_user,
+ detail::refcnt_t(new detail::refcnt_t::element_type(0)))));
+ INV_MSG(_pimpl->all_exec_handles.find(handle.pid()) ==
+ _pimpl->all_exec_handles.end(),
+ F("PID %s already in all_exec_handles; not properly cleaned "
+ "up or reused too fast") % handle.pid());;
+ _pimpl->all_exec_handles.insert(exec_handles_map::value_type(
+ handle.pid(), handle));
+ LI(F("Spawned subprocess with exec_handle %s") % handle.pid());
+ return handle;
+}
+
+
+/// Pre-helper for the spawn_followup() method.
+void
+executor::executor_handle::spawn_followup_pre(void)
+{
+ signals::check_interrupt();
+}
+
+
+/// Post-helper for the spawn_followup() method.
+///
+/// \param base Exit handle of the subprocess to use as context.
+/// \param timeout Maximum amount of time the subprocess can run for.
+/// \param child The process created by spawn_followup().
+///
+/// \return The execution handle of the started subprocess.
+executor::exec_handle
+executor::executor_handle::spawn_followup_post(
+ const exit_handle& base,
+ const datetime::delta& timeout,
+ std::auto_ptr< process::child > child)
+{
+ INV(*base.state_owners() > 0);
+ const exec_handle handle(std::shared_ptr< exec_handle::impl >(
+ new exec_handle::impl(
+ child->pid(),
+ base.control_directory(),
+ base.stdout_file(),
+ base.stderr_file(),
+ datetime::timestamp::now(),
+ timeout,
+ base.unprivileged_user(),
+ base.state_owners())));
+ INV_MSG(_pimpl->all_exec_handles.find(handle.pid()) ==
+ _pimpl->all_exec_handles.end(),
+ F("PID %s already in all_exec_handles; not properly cleaned "
+ "up or reused too fast") % handle.pid());;
+ _pimpl->all_exec_handles.insert(exec_handles_map::value_type(
+ handle.pid(), handle));
+ LI(F("Spawned subprocess with exec_handle %s") % handle.pid());
+ return handle;
+}
+
+
+/// Waits for completion of any forked process.
+///
+/// \param exec_handle The handle of the process to wait for.
+///
+/// \return A pointer to an object describing the waited-for subprocess.
+executor::exit_handle
+executor::executor_handle::wait(const exec_handle exec_handle)
+{
+ signals::check_interrupt();
+ const process::status status = process::wait(exec_handle.pid());
+ return _pimpl->post_wait(exec_handle.pid(), status);
+}
+
+
+/// Waits for completion of any forked process.
+///
+/// \return A pointer to an object describing the waited-for subprocess.
+executor::exit_handle
+executor::executor_handle::wait_any(void)
+{
+ signals::check_interrupt();
+ const process::status status = process::wait_any();
+ return _pimpl->post_wait(status.dead_pid(), status);
+}
+
+
+/// Checks if an interrupt has fired.
+///
+/// Calls to this function should be sprinkled in strategic places through the
+/// code protected by an interrupts_handler object.
+///
+/// This is just a wrapper over signals::check_interrupt() to avoid leaking this
+/// dependency to the caller.
+///
+/// \throw signals::interrupted_error If there has been an interrupt.
+void
+executor::executor_handle::check_interrupt(void) const
+{
+ signals::check_interrupt();
+}
diff --git a/utils/process/executor.hpp b/utils/process/executor.hpp
new file mode 100644
index 000000000000..858ad9c815aa
--- /dev/null
+++ b/utils/process/executor.hpp
@@ -0,0 +1,231 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/executor.hpp
+/// Multiprogrammed process executor with isolation guarantees.
+///
+/// This module provides a mechanism to invoke more than one process
+/// concurrently while at the same time ensuring that each process is run
+/// in a clean container and in a "safe" work directory that gets cleaned
+/// up automatically on termination.
+///
+/// The intended workflow for using this module is the following:
+///
+/// 1) Initialize the executor using setup(). Keep the returned object
+/// around through the lifetime of the next operations. Only one
+/// instance of the executor can be alive at once.
+/// 2) Spawn one or more processes with spawn(). On the caller side, keep
+/// track of any per-process data you may need using the returned
+/// exec_handle, which is unique among the set of active processes.
+/// 3) Call wait() or wait_any() to wait for completion of a process started
+/// in the previous step. Repeat as desired.
+/// 4) Use the returned exit_handle object by wait() or wait_any() to query
+/// the status of the terminated process and/or to access any of its
+/// data files.
+/// 5) Invoke cleanup() on the exit_handle to wipe any stale data.
+/// 6) Invoke cleanup() on the object returned by setup().
+///
+/// It is the responsibility of the caller to ensure that calls to
+/// spawn() and spawn_followup() are balanced with wait() and wait_any() calls.
+///
+/// Processes executed in this manner have access to two different "unique"
+/// directories: the first is the "work directory", which is an empty directory
+/// that acts as the subprocess' work directory; the second is the "control
+/// directory", which is the location where the in-process code may place files
+/// that are not clobbered by activities in the work directory.
+
+#if !defined(UTILS_PROCESS_EXECUTOR_HPP)
+#define UTILS_PROCESS_EXECUTOR_HPP
+
+#include "utils/process/executor_fwd.hpp"
+
+#include <cstddef>
+#include <memory>
+
+#include "utils/datetime_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional.hpp"
+#include "utils/passwd_fwd.hpp"
+#include "utils/process/child_fwd.hpp"
+#include "utils/process/status_fwd.hpp"
+
+namespace utils {
+namespace process {
+namespace executor {
+
+
+namespace detail {
+
+
+extern const char* stdout_name;
+extern const char* stderr_name;
+extern const char* work_subdir;
+
+
+/// Shared reference counter.
+typedef std::shared_ptr< std::size_t > refcnt_t;
+
+
+void setup_child(const utils::optional< utils::passwd::user >,
+ const utils::fs::path&, const utils::fs::path&);
+
+
+} // namespace detail
+
+
+/// Maintenance data held while a subprocess is being executed.
+///
+/// This data structure exists from the moment a subprocess is executed via
+/// executor::spawn() to when it is cleaned up with exit_handle::cleanup().
+///
+/// The caller NEED NOT maintain this object alive for the execution of the
+/// subprocess. However, the PID contained in here can be used to match
+/// exec_handle objects with corresponding exit_handle objects via their
+/// original_pid() method.
+///
+/// Objects of this type can be copied around but their implementation is
+/// shared. The implication of this is that only the last copy of a given exit
+/// handle will execute the automatic cleanup() on destruction.
+class exec_handle {
+ struct impl;
+
+ /// Pointer to internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class executor_handle;
+ exec_handle(std::shared_ptr< impl >);
+
+public:
+ ~exec_handle(void);
+
+ int pid(void) const;
+ utils::fs::path control_directory(void) const;
+ utils::fs::path work_directory(void) const;
+ const utils::fs::path& stdout_file(void) const;
+ const utils::fs::path& stderr_file(void) const;
+};
+
+
+/// Container for the data of a process termination.
+///
+/// This handle provides access to the details of the process that terminated
+/// and serves as the owner of the remaining on-disk files. The caller is
+/// expected to call cleanup() before destruction to remove the on-disk state.
+///
+/// Objects of this type can be copied around but their implementation is
+/// shared. The implication of this is that only the last copy of a given exit
+/// handle will execute the automatic cleanup() on destruction.
+class exit_handle {
+ struct impl;
+
+ /// Pointer to internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class executor_handle;
+ exit_handle(std::shared_ptr< impl >);
+
+ detail::refcnt_t state_owners(void) const;
+
+public:
+ ~exit_handle(void);
+
+ void cleanup(void);
+
+ int original_pid(void) const;
+ const utils::optional< utils::process::status >& status(void) const;
+ const utils::optional< utils::passwd::user >& unprivileged_user(void) const;
+ const utils::datetime::timestamp& start_time() const;
+ const utils::datetime::timestamp& end_time() const;
+ utils::fs::path control_directory(void) const;
+ utils::fs::path work_directory(void) const;
+ const utils::fs::path& stdout_file(void) const;
+ const utils::fs::path& stderr_file(void) const;
+};
+
+
+/// Handler for the livelihood of the executor.
+///
+/// Objects of this type can be copied around (because we do not have move
+/// semantics...) but their implementation is shared. Only one instance of the
+/// executor can exist at any point in time.
+class executor_handle {
+ struct impl;
+ /// Pointer to internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend executor_handle setup(void);
+ executor_handle(void) throw();
+
+ utils::fs::path spawn_pre(void);
+ exec_handle spawn_post(const utils::fs::path&,
+ const utils::fs::path&,
+ const utils::fs::path&,
+ const utils::datetime::delta&,
+ const utils::optional< utils::passwd::user >,
+ std::auto_ptr< utils::process::child >);
+
+ void spawn_followup_pre(void);
+ exec_handle spawn_followup_post(const exit_handle&,
+ const utils::datetime::delta&,
+ std::auto_ptr< utils::process::child >);
+
+public:
+ ~executor_handle(void);
+
+ const utils::fs::path& root_work_directory(void) const;
+
+ void cleanup(void);
+
+ template< class Hook >
+ exec_handle spawn(Hook,
+ const datetime::delta&,
+ const utils::optional< utils::passwd::user >,
+ const utils::optional< utils::fs::path > = utils::none,
+ const utils::optional< utils::fs::path > = utils::none);
+
+ template< class Hook >
+ exec_handle spawn_followup(Hook,
+ const exit_handle&,
+ const datetime::delta&);
+
+ exit_handle wait(const exec_handle);
+ exit_handle wait_any(void);
+
+ void check_interrupt(void) const;
+};
+
+
+executor_handle setup(void);
+
+
+} // namespace executor
+} // namespace process
+} // namespace utils
+
+
+#endif // !defined(UTILS_PROCESS_EXECUTOR_HPP)
diff --git a/utils/process/executor.ipp b/utils/process/executor.ipp
new file mode 100644
index 000000000000..e91f994673d7
--- /dev/null
+++ b/utils/process/executor.ipp
@@ -0,0 +1,182 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_PROCESS_EXECUTOR_IPP)
+#define UTILS_PROCESS_EXECUTOR_IPP
+
+#include "utils/process/executor.hpp"
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/process/child.ipp"
+
+namespace utils {
+namespace process {
+
+
+namespace executor {
+namespace detail {
+
+/// Functor to execute a hook in a child process.
+///
+/// The hook is executed after the process has been isolated per the logic in
+/// utils::process::isolation based on the input parameters at construction
+/// time.
+template< class Hook >
+class run_child {
+ /// Function or functor to invoke in the child.
+ Hook _hook;
+
+ /// Directory where the hook may place control files.
+ const fs::path& _control_directory;
+
+ /// Directory to enter when running the subprocess.
+ ///
+ /// This is a subdirectory of _control_directory but is separate so that
+ /// subprocess operations do not inadvertently affect our files.
+ const fs::path& _work_directory;
+
+ /// User to switch to when running the subprocess.
+ ///
+ /// If not none, the subprocess will be executed as the provided user and
+ /// the control and work directories will be writable by this user.
+ const optional< passwd::user > _unprivileged_user;
+
+public:
+ /// Constructor.
+ ///
+ /// \param hook Function or functor to invoke in the child.
+ /// \param control_directory Directory where control files can be placed.
+ /// \param work_directory Directory to enter when running the subprocess.
+ /// \param unprivileged_user If set, user to switch to before execution.
+ run_child(Hook hook,
+ const fs::path& control_directory,
+ const fs::path& work_directory,
+ const optional< passwd::user > unprivileged_user) :
+ _hook(hook),
+ _control_directory(control_directory),
+ _work_directory(work_directory),
+ _unprivileged_user(unprivileged_user)
+ {
+ }
+
+ /// Body of the subprocess.
+ void
+ operator()(void)
+ {
+ executor::detail::setup_child(_unprivileged_user,
+ _control_directory, _work_directory);
+ _hook(_control_directory);
+ }
+};
+
+} // namespace detail
+} // namespace executor
+
+
+/// Forks and executes a subprocess asynchronously.
+///
+/// \tparam Hook Type of the hook.
+/// \param hook Function or functor to run in the subprocess.
+/// \param timeout Maximum amount of time the subprocess can run for.
+/// \param unprivileged_user If not none, user to switch to before execution.
+/// \param stdout_target If not none, file to which to write the stdout of the
+/// test case.
+/// \param stderr_target If not none, file to which to write the stderr of the
+/// test case.
+///
+/// \return A handle for the background operation. Used to match the result of
+/// the execution returned by wait_any() with this invocation.
+template< class Hook >
+executor::exec_handle
+executor::executor_handle::spawn(
+ Hook hook,
+ const datetime::delta& timeout,
+ const optional< passwd::user > unprivileged_user,
+ const optional< fs::path > stdout_target,
+ const optional< fs::path > stderr_target)
+{
+ const fs::path unique_work_directory = spawn_pre();
+
+ const fs::path stdout_path = stdout_target ?
+ stdout_target.get() : (unique_work_directory / detail::stdout_name);
+ const fs::path stderr_path = stderr_target ?
+ stderr_target.get() : (unique_work_directory / detail::stderr_name);
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ detail::run_child< Hook >(hook,
+ unique_work_directory,
+ unique_work_directory / detail::work_subdir,
+ unprivileged_user),
+ stdout_path, stderr_path);
+
+ return spawn_post(unique_work_directory, stdout_path, stderr_path,
+ timeout, unprivileged_user, child);
+}
+
+
+/// Forks and executes a subprocess asynchronously in the context of another.
+///
+/// By context we understand the on-disk state of a previously-executed process,
+/// thus the new subprocess spawned by this function will run with the same
+/// control and work directories as another process.
+///
+/// \tparam Hook Type of the hook.
+/// \param hook Function or functor to run in the subprocess.
+/// \param base Context of the subprocess in which to run this one. The
+/// exit_handle provided here must remain alive throughout the existence of
+/// this other object because the original exit_handle is the one that owns
+/// the on-disk state.
+/// \param timeout Maximum amount of time the subprocess can run for.
+///
+/// \return A handle for the background operation. Used to match the result of
+/// the execution returned by wait_any() with this invocation.
+template< class Hook >
+executor::exec_handle
+executor::executor_handle::spawn_followup(Hook hook,
+ const exit_handle& base,
+ const datetime::delta& timeout)
+{
+ spawn_followup_pre();
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ detail::run_child< Hook >(hook,
+ base.control_directory(),
+ base.work_directory(),
+ base.unprivileged_user()),
+ base.stdout_file(), base.stderr_file());
+
+ return spawn_followup_post(base, timeout, child);
+}
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_EXECUTOR_IPP)
diff --git a/utils/process/executor_fwd.hpp b/utils/process/executor_fwd.hpp
new file mode 100644
index 000000000000..ec63227993f3
--- /dev/null
+++ b/utils/process/executor_fwd.hpp
@@ -0,0 +1,49 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/executor_fwd.hpp
+/// Forward declarations for utils/process/executor.hpp
+
+#if !defined(UTILS_PROCESS_EXECUTOR_FWD_HPP)
+#define UTILS_PROCESS_EXECUTOR_FWD_HPP
+
+namespace utils {
+namespace process {
+namespace executor {
+
+
+class exec_handle;
+class executor_handle;
+class exit_handle;
+
+
+} // namespace executor
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_EXECUTOR_FWD_HPP)
diff --git a/utils/process/executor_test.cpp b/utils/process/executor_test.cpp
new file mode 100644
index 000000000000..13ae69bd44ed
--- /dev/null
+++ b/utils/process/executor_test.cpp
@@ -0,0 +1,940 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/executor.ipp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/defs.hpp"
+#include "utils/env.hpp"
+#include "utils/format/containers.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/stacktrace.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace datetime = utils::datetime;
+namespace executor = utils::process::executor;
+namespace fs = utils::fs;
+namespace passwd = utils::passwd;
+namespace process = utils::process;
+namespace signals = utils::signals;
+namespace text = utils::text;
+
+using utils::none;
+using utils::optional;
+
+
+/// Large timeout for the processes we spawn.
+///
+/// This number is supposed to be (much) larger than the timeout of the test
+/// cases that use it so that children processes are not killed spuriously.
+static const datetime::delta infinite_timeout(1000000, 0);
+
+
+static void do_exit(const int) UTILS_NORETURN;
+
+
+/// Terminates a subprocess without invoking destructors.
+///
+/// This is just a simple wrapper over _exit(2) because we cannot use std::exit
+/// on exit from a subprocess. The reason is that we do not want to invoke any
+/// destructors as otherwise we'd clear up the global executor state by mistake.
+/// This wouldn't be a major problem if it wasn't because doing so deletes
+/// on-disk files and we want to leave them in place so that the parent process
+/// can test for them!
+///
+/// \param exit_code Code to exit with.
+static void
+do_exit(const int exit_code)
+{
+ std::cout.flush();
+ std::cerr.flush();
+ ::_exit(exit_code);
+}
+
+
+/// Subprocess that creates a cookie file in its work directory.
+class child_create_cookie {
+ /// Name of the cookie to create.
+ const std::string _cookie_name;
+
+public:
+ /// Constructor.
+ ///
+ /// \param cookie_name Name of the cookie to create.
+ child_create_cookie(const std::string& cookie_name) :
+ _cookie_name(cookie_name)
+ {
+ }
+
+ /// Runs the subprocess.
+ void
+ operator()(const fs::path& /* control_directory */)
+ UTILS_NORETURN
+ {
+ std::cout << "Creating cookie: " << _cookie_name << " (stdout)\n";
+ std::cerr << "Creating cookie: " << _cookie_name << " (stderr)\n";
+ atf::utils::create_file(_cookie_name, "");
+ do_exit(EXIT_SUCCESS);
+ }
+};
+
+
+static void child_delete_all(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that deletes all files in the current directory.
+///
+/// This is intended to validate that the test runs in an empty directory,
+/// separate from any control files that the executor may have created.
+///
+/// \param control_directory Directory where control files separate from the
+/// work directory can be placed.
+static void
+child_delete_all(const fs::path& control_directory)
+{
+ const fs::path cookie = control_directory / "exec_was_called";
+ std::ofstream control_file(cookie.c_str());
+ if (!control_file) {
+ std::cerr << "Failed to create " << cookie << '\n';
+ std::abort();
+ }
+
+ const int exit_code = ::system("rm *") == -1
+ ? EXIT_FAILURE : EXIT_SUCCESS;
+ do_exit(exit_code);
+}
+
+
+static void child_dump_unprivileged_user(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that dumps user configuration.
+static void
+child_dump_unprivileged_user(const fs::path& /* control_directory */)
+{
+ const passwd::user current_user = passwd::current_user();
+ std::cout << F("UID = %s\n") % current_user.uid;
+ do_exit(EXIT_SUCCESS);
+}
+
+
+/// Subprocess that returns a specific exit code.
+class child_exit {
+ /// Exit code to return.
+ int _exit_code;
+
+public:
+ /// Constructor.
+ ///
+ /// \param exit_code Exit code to return.
+ child_exit(const int exit_code) : _exit_code(exit_code)
+ {
+ }
+
+ /// Runs the subprocess.
+ void
+ operator()(const fs::path& /* control_directory */)
+ UTILS_NORETURN
+ {
+ do_exit(_exit_code);
+ }
+};
+
+
+static void child_pause(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that just blocks.
+static void
+child_pause(const fs::path& /* control_directory */)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ for (;;) {
+ ::sigsuspend(&mask);
+ }
+ std::abort();
+}
+
+
+static void child_print(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that writes to stdout and stderr.
+static void
+child_print(const fs::path& /* control_directory */)
+{
+ std::cout << "stdout: some text\n";
+ std::cerr << "stderr: some other text\n";
+
+ do_exit(EXIT_SUCCESS);
+}
+
+
+/// Subprocess that sleeps for a period of time before exiting.
+class child_sleep {
+ /// Seconds to sleep for before termination.
+ int _seconds;
+
+public:
+ /// Construtor.
+ ///
+ /// \param seconds Seconds to sleep for before termination.
+ child_sleep(const int seconds) : _seconds(seconds)
+ {
+ }
+
+ /// Runs the subprocess.
+ void
+ operator()(const fs::path& /* control_directory */)
+ UTILS_NORETURN
+ {
+ ::sleep(_seconds);
+ do_exit(EXIT_SUCCESS);
+ }
+};
+
+
+static void child_spawn_blocking_child(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that spawns a subchild that gets stuck.
+///
+/// Used by the caller to validate that the whole process tree is terminated
+/// when this subprocess is killed.
+static void
+child_spawn_blocking_child(
+ const fs::path& /* control_directory */)
+{
+ pid_t pid = ::fork();
+ if (pid == -1) {
+ std::cerr << "Cannot fork subprocess\n";
+ do_exit(EXIT_FAILURE);
+ } else if (pid == 0) {
+ for (;;)
+ ::pause();
+ } else {
+ const fs::path name = fs::path(utils::getenv("CONTROL_DIR").get()) /
+ "pid";
+ std::ofstream pidfile(name.c_str());
+ if (!pidfile) {
+ std::cerr << "Failed to create the pidfile\n";
+ do_exit(EXIT_FAILURE);
+ }
+ pidfile << pid;
+ pidfile.close();
+ do_exit(EXIT_SUCCESS);
+ }
+}
+
+
+static void child_validate_isolation(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that checks if isolate_child() has been called.
+static void
+child_validate_isolation(const fs::path& /* control_directory */)
+{
+ if (utils::getenv("HOME").get() == "fake-value") {
+ std::cerr << "HOME not reset\n";
+ do_exit(EXIT_FAILURE);
+ }
+ if (utils::getenv("LANG")) {
+ std::cerr << "LANG not unset\n";
+ do_exit(EXIT_FAILURE);
+ }
+ do_exit(EXIT_SUCCESS);
+}
+
+
+/// Invokes executor::spawn() with default arguments.
+///
+/// \param handle The executor on which to invoke spawn().
+/// \param args Arguments to the binary.
+/// \param timeout Maximum time the program can run for.
+/// \param unprivileged_user If set, user to switch to when running the child
+/// program.
+/// \param stdout_target If not none, file to which to write the stdout of the
+/// test case.
+/// \param stderr_target If not none, file to which to write the stderr of the
+/// test case.
+///
+/// \return The exec handle for the spawned binary.
+template< class Hook >
+static executor::exec_handle
+do_spawn(executor::executor_handle& handle, Hook hook,
+ const datetime::delta& timeout = infinite_timeout,
+ const optional< passwd::user > unprivileged_user = none,
+ const optional< fs::path > stdout_target = none,
+ const optional< fs::path > stderr_target = none)
+{
+ const executor::exec_handle exec_handle = handle.spawn< Hook >(
+ hook, timeout, unprivileged_user, stdout_target, stderr_target);
+ return exec_handle;
+}
+
+
+/// Checks for a specific exit status in the status of a exit_handle.
+///
+/// \param exit_status The expected exit status.
+/// \param status The value of exit_handle.status().
+///
+/// \post Terminates the calling test case if the status does not match the
+/// required value.
+static void
+require_exit(const int exit_status, const optional< process::status > status)
+{
+ ATF_REQUIRE(status);
+ ATF_REQUIRE(status.get().exited());
+ ATF_REQUIRE_EQ(exit_status, status.get().exitstatus());
+}
+
+
+/// Ensures that a killed process is gone.
+///
+/// The way we do this is by sending an idempotent signal to the given PID
+/// and checking if the signal was delivered. If it was, the process is
+/// still alive; if it was not, then it is gone.
+///
+/// Note that this might be inaccurate for two reasons:
+///
+/// 1) The system may have spawned a new process with the same pid as
+/// our subchild... but in practice, this does not happen because
+/// most systems do not immediately reuse pid numbers. If that
+/// happens... well, we get a false test failure.
+///
+/// 2) We ran so fast that even if the process was sent a signal to
+/// die, it has not had enough time to process it yet. This is why
+/// we retry this a few times.
+///
+/// \param pid PID of the process to check.
+static void
+ensure_dead(const pid_t pid)
+{
+ int attempts = 30;
+retry:
+ if (::kill(pid, SIGCONT) != -1 || errno != ESRCH) {
+ if (attempts > 0) {
+ std::cout << "Subprocess not dead yet; retrying wait\n";
+ --attempts;
+ ::usleep(100000);
+ goto retry;
+ }
+ ATF_FAIL(F("The subprocess %s of our child was not killed") % pid);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__run_one);
+ATF_TEST_CASE_BODY(integration__run_one)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const executor::exec_handle exec_handle = do_spawn(handle, child_exit(41));
+
+ executor::exit_handle exit_handle = handle.wait_any();
+ ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
+ require_exit(41, exit_handle.status());
+ exit_handle.cleanup();
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__run_many);
+ATF_TEST_CASE_BODY(integration__run_many)
+{
+ static const std::size_t num_children = 30;
+
+ executor::executor_handle handle = executor::setup();
+
+ std::size_t total_children = 0;
+ std::map< int, int > exp_exit_statuses;
+ std::map< int, datetime::timestamp > exp_start_times;
+ for (std::size_t i = 0; i < num_children; ++i) {
+ const datetime::timestamp start_time = datetime::timestamp::from_values(
+ 2014, 12, 8, 9, 40, 0, i);
+
+ for (std::size_t j = 0; j < 3; j++) {
+ const std::size_t id = i * 3 + j;
+
+ datetime::set_mock_now(start_time);
+ const int pid = do_spawn(handle, child_exit(id)).pid();
+ exp_exit_statuses.insert(std::make_pair(pid, id));
+ exp_start_times.insert(std::make_pair(pid, start_time));
+ ++total_children;
+ }
+ }
+
+ for (std::size_t i = 0; i < total_children; ++i) {
+ const datetime::timestamp end_time = datetime::timestamp::from_values(
+ 2014, 12, 8, 9, 50, 10, i);
+ datetime::set_mock_now(end_time);
+ executor::exit_handle exit_handle = handle.wait_any();
+ const int original_pid = exit_handle.original_pid();
+
+ const int exit_status = exp_exit_statuses.find(original_pid)->second;
+ const datetime::timestamp& start_time = exp_start_times.find(
+ original_pid)->second;
+
+ require_exit(exit_status, exit_handle.status());
+
+ ATF_REQUIRE_EQ(start_time, exit_handle.start_time());
+ ATF_REQUIRE_EQ(end_time, exit_handle.end_time());
+
+ exit_handle.cleanup();
+
+ ATF_REQUIRE(!atf::utils::file_exists(
+ exit_handle.stdout_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(
+ exit_handle.work_directory().str()));
+ }
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__parameters_and_output);
+ATF_TEST_CASE_BODY(integration__parameters_and_output)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const executor::exec_handle exec_handle = do_spawn(handle, child_print);
+
+ executor::exit_handle exit_handle = handle.wait_any();
+
+ ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
+
+ require_exit(EXIT_SUCCESS, exit_handle.status());
+
+ const fs::path stdout_file = exit_handle.stdout_file();
+ ATF_REQUIRE(atf::utils::compare_file(
+ stdout_file.str(), "stdout: some text\n"));
+ const fs::path stderr_file = exit_handle.stderr_file();
+ ATF_REQUIRE(atf::utils::compare_file(
+ stderr_file.str(), "stderr: some other text\n"));
+
+ exit_handle.cleanup();
+ ATF_REQUIRE(!fs::exists(stdout_file));
+ ATF_REQUIRE(!fs::exists(stderr_file));
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__custom_output_files);
+ATF_TEST_CASE_BODY(integration__custom_output_files)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const fs::path stdout_file("custom-stdout.txt");
+ const fs::path stderr_file("custom-stderr.txt");
+
+ const executor::exec_handle exec_handle = do_spawn(
+ handle, child_print, infinite_timeout, none,
+ utils::make_optional(stdout_file),
+ utils::make_optional(stderr_file));
+
+ executor::exit_handle exit_handle = handle.wait_any();
+
+ ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
+
+ require_exit(EXIT_SUCCESS, exit_handle.status());
+
+ ATF_REQUIRE_EQ(stdout_file, exit_handle.stdout_file());
+ ATF_REQUIRE_EQ(stderr_file, exit_handle.stderr_file());
+
+ exit_handle.cleanup();
+
+ handle.cleanup();
+
+ // Must compare after cleanup to ensure the files did not get deleted.
+ ATF_REQUIRE(atf::utils::compare_file(
+ stdout_file.str(), "stdout: some text\n"));
+ ATF_REQUIRE(atf::utils::compare_file(
+ stderr_file.str(), "stderr: some other text\n"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__timestamps);
+ATF_TEST_CASE_BODY(integration__timestamps)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const datetime::timestamp start_time = datetime::timestamp::from_values(
+ 2014, 12, 8, 9, 35, 10, 1000);
+ const datetime::timestamp end_time = datetime::timestamp::from_values(
+ 2014, 12, 8, 9, 35, 20, 2000);
+
+ datetime::set_mock_now(start_time);
+ do_spawn(handle, child_exit(70));
+
+ datetime::set_mock_now(end_time);
+ executor::exit_handle exit_handle = handle.wait_any();
+
+ require_exit(70, exit_handle.status());
+
+ ATF_REQUIRE_EQ(start_time, exit_handle.start_time());
+ ATF_REQUIRE_EQ(end_time, exit_handle.end_time());
+ exit_handle.cleanup();
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__files);
+ATF_TEST_CASE_BODY(integration__files)
+{
+ executor::executor_handle handle = executor::setup();
+
+ do_spawn(handle, child_create_cookie("cookie.12345"));
+
+ executor::exit_handle exit_handle = handle.wait_any();
+
+ ATF_REQUIRE(atf::utils::file_exists(
+ (exit_handle.work_directory() / "cookie.12345").str()));
+
+ exit_handle.cleanup();
+
+ ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stdout_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stderr_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(exit_handle.work_directory().str()));
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__followup);
+ATF_TEST_CASE_BODY(integration__followup)
+{
+ executor::executor_handle handle = executor::setup();
+
+ (void)handle.spawn(child_create_cookie("cookie.1"), infinite_timeout, none);
+ executor::exit_handle exit_1_handle = handle.wait_any();
+
+ (void)handle.spawn_followup(child_create_cookie("cookie.2"), exit_1_handle,
+ infinite_timeout);
+ executor::exit_handle exit_2_handle = handle.wait_any();
+
+ ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_2_handle.stdout_file());
+ ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_2_handle.stderr_file());
+ ATF_REQUIRE_EQ(exit_1_handle.control_directory(),
+ exit_2_handle.control_directory());
+ ATF_REQUIRE_EQ(exit_1_handle.work_directory(),
+ exit_2_handle.work_directory());
+
+ (void)handle.spawn_followup(child_create_cookie("cookie.3"), exit_2_handle,
+ infinite_timeout);
+ exit_2_handle.cleanup();
+ exit_1_handle.cleanup();
+ executor::exit_handle exit_3_handle = handle.wait_any();
+
+ ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_3_handle.stdout_file());
+ ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_3_handle.stderr_file());
+ ATF_REQUIRE_EQ(exit_1_handle.control_directory(),
+ exit_3_handle.control_directory());
+ ATF_REQUIRE_EQ(exit_1_handle.work_directory(),
+ exit_3_handle.work_directory());
+
+ ATF_REQUIRE(atf::utils::file_exists(
+ (exit_1_handle.work_directory() / "cookie.1").str()));
+ ATF_REQUIRE(atf::utils::file_exists(
+ (exit_1_handle.work_directory() / "cookie.2").str()));
+ ATF_REQUIRE(atf::utils::file_exists(
+ (exit_1_handle.work_directory() / "cookie.3").str()));
+
+ ATF_REQUIRE(atf::utils::compare_file(
+ exit_1_handle.stdout_file().str(),
+ "Creating cookie: cookie.1 (stdout)\n"
+ "Creating cookie: cookie.2 (stdout)\n"
+ "Creating cookie: cookie.3 (stdout)\n"));
+
+ ATF_REQUIRE(atf::utils::compare_file(
+ exit_1_handle.stderr_file().str(),
+ "Creating cookie: cookie.1 (stderr)\n"
+ "Creating cookie: cookie.2 (stderr)\n"
+ "Creating cookie: cookie.3 (stderr)\n"));
+
+ exit_3_handle.cleanup();
+
+ ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stdout_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stderr_file().str()));
+ ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.work_directory().str()));
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__output_files_always_exist);
+ATF_TEST_CASE_BODY(integration__output_files_always_exist)
+{
+ executor::executor_handle handle = executor::setup();
+
+ // This test is racy: we specify a very short timeout for the subprocess so
+ // that we cause the subprocess to exit before it has had time to set up the
+ // output files. However, for scheduling reasons, the subprocess may
+ // actually run to completion before the timer triggers. Retry this a few
+ // times to attempt to catch a "good test".
+ for (int i = 0; i < 50; i++) {
+ const executor::exec_handle exec_handle =
+ do_spawn(handle, child_exit(0), datetime::delta(0, 100000));
+ executor::exit_handle exit_handle = handle.wait(exec_handle);
+ ATF_REQUIRE(fs::exists(exit_handle.stdout_file()));
+ ATF_REQUIRE(fs::exists(exit_handle.stderr_file()));
+ exit_handle.cleanup();
+ }
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE(integration__timeouts);
+ATF_TEST_CASE_HEAD(integration__timeouts)
+{
+ set_md_var("timeout", "60");
+}
+ATF_TEST_CASE_BODY(integration__timeouts)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const executor::exec_handle exec_handle1 =
+ do_spawn(handle, child_sleep(30), datetime::delta(2, 0));
+ const executor::exec_handle exec_handle2 =
+ do_spawn(handle, child_sleep(40), datetime::delta(5, 0));
+ const executor::exec_handle exec_handle3 =
+ do_spawn(handle, child_exit(15));
+
+ {
+ executor::exit_handle exit_handle = handle.wait_any();
+ ATF_REQUIRE_EQ(exec_handle3.pid(), exit_handle.original_pid());
+ require_exit(15, exit_handle.status());
+ exit_handle.cleanup();
+ }
+
+ {
+ executor::exit_handle exit_handle = handle.wait_any();
+ ATF_REQUIRE_EQ(exec_handle1.pid(), exit_handle.original_pid());
+ ATF_REQUIRE(!exit_handle.status());
+ const datetime::delta duration =
+ exit_handle.end_time() - exit_handle.start_time();
+ ATF_REQUIRE(duration < datetime::delta(10, 0));
+ ATF_REQUIRE(duration >= datetime::delta(2, 0));
+ exit_handle.cleanup();
+ }
+
+ {
+ executor::exit_handle exit_handle = handle.wait_any();
+ ATF_REQUIRE_EQ(exec_handle2.pid(), exit_handle.original_pid());
+ ATF_REQUIRE(!exit_handle.status());
+ const datetime::delta duration =
+ exit_handle.end_time() - exit_handle.start_time();
+ ATF_REQUIRE(duration < datetime::delta(10, 0));
+ ATF_REQUIRE(duration >= datetime::delta(4, 0));
+ exit_handle.cleanup();
+ }
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE(integration__unprivileged_user);
+ATF_TEST_CASE_HEAD(integration__unprivileged_user)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(integration__unprivileged_user)
+{
+ executor::executor_handle handle = executor::setup();
+
+ const passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+
+ do_spawn(handle, child_dump_unprivileged_user,
+ infinite_timeout, utils::make_optional(unprivileged_user));
+
+ executor::exit_handle exit_handle = handle.wait_any();
+ ATF_REQUIRE(atf::utils::compare_file(
+ exit_handle.stdout_file().str(),
+ F("UID = %s\n") % unprivileged_user.uid));
+ exit_handle.cleanup();
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__auto_cleanup);
+ATF_TEST_CASE_BODY(integration__auto_cleanup)
+{
+ std::vector< int > pids;
+ std::vector< fs::path > paths;
+ {
+ executor::executor_handle handle = executor::setup();
+
+ pids.push_back(do_spawn(handle, child_exit(10)).pid());
+ pids.push_back(do_spawn(handle, child_exit(20)).pid());
+
+ // This invocation is never waited for below. This is intentional: we
+ // want the destructor to clean the "leaked" test automatically so that
+ // the clean up of the parent work directory also happens correctly.
+ pids.push_back(do_spawn(handle, child_pause).pid());
+
+ executor::exit_handle exit_handle1 = handle.wait_any();
+ paths.push_back(exit_handle1.stdout_file());
+ paths.push_back(exit_handle1.stderr_file());
+ paths.push_back(exit_handle1.work_directory());
+
+ executor::exit_handle exit_handle2 = handle.wait_any();
+ paths.push_back(exit_handle2.stdout_file());
+ paths.push_back(exit_handle2.stderr_file());
+ paths.push_back(exit_handle2.work_directory());
+ }
+ for (std::vector< int >::const_iterator iter = pids.begin();
+ iter != pids.end(); ++iter) {
+ ensure_dead(*iter);
+ }
+ for (std::vector< fs::path >::const_iterator iter = paths.begin();
+ iter != paths.end(); ++iter) {
+ ATF_REQUIRE(!atf::utils::file_exists((*iter).str()));
+ }
+}
+
+
+/// Ensures that interrupting an executor cleans things up correctly.
+///
+/// This test scenario is tricky. We spawn a master child process that runs the
+/// executor code and we send a signal to it externally. The child process
+/// spawns a bunch of tests that block indefinitely and tries to wait for their
+/// results. When the signal is received, we expect an interrupt_error to be
+/// raised, which in turn should clean up all test resources and exit the master
+/// child process successfully.
+///
+/// \param signo Signal to deliver to the executor.
+static void
+do_signal_handling_test(const int signo)
+{
+ static const char* cookie = "spawned.txt";
+
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ static const std::size_t num_children = 3;
+
+ optional< fs::path > root_work_directory;
+ try {
+ executor::executor_handle handle = executor::setup();
+ root_work_directory = handle.root_work_directory();
+
+ for (std::size_t i = 0; i < num_children; ++i) {
+ std::cout << "Spawned child number " << i << '\n';
+ do_spawn(handle, child_pause);
+ }
+
+ std::cout << "Creating " << cookie << " cookie\n";
+ atf::utils::create_file(cookie, "");
+
+ std::cout << "Waiting for subprocess termination\n";
+ for (std::size_t i = 0; i < num_children; ++i) {
+ executor::exit_handle exit_handle = handle.wait_any();
+ // We may never reach this point in the test, but if we do let's
+ // make sure the subprocess was terminated as expected.
+ if (exit_handle.status()) {
+ if (exit_handle.status().get().signaled() &&
+ exit_handle.status().get().termsig() == SIGKILL) {
+ // OK.
+ } else {
+ std::cerr << "Child exited with unexpected code: "
+ << exit_handle.status().get();
+ std::exit(EXIT_FAILURE);
+ }
+ } else {
+ std::cerr << "Child timed out\n";
+ std::exit(EXIT_FAILURE);
+ }
+ exit_handle.cleanup();
+ }
+ std::cerr << "Terminating without reception of signal\n";
+ std::exit(EXIT_FAILURE);
+ } catch (const signals::interrupted_error& unused_error) {
+ std::cerr << "Terminating due to interrupted_error\n";
+ // We never kill ourselves until the cookie is created, so it is
+ // guaranteed that the optional root_work_directory has been
+ // initialized at this point.
+ if (atf::utils::file_exists(root_work_directory.get().str())) {
+ // Some cleanup did not happen; error out.
+ std::exit(EXIT_FAILURE);
+ } else {
+ std::exit(EXIT_SUCCESS);
+ }
+ }
+ std::abort();
+ }
+
+ std::cout << "Waiting for " << cookie << " cookie creation\n";
+ while (!atf::utils::file_exists(cookie)) {
+ // Wait for processes.
+ }
+ ATF_REQUIRE(::unlink(cookie) != -1);
+ std::cout << "Killing process\n";
+ ATF_REQUIRE(::kill(pid, signo) != -1);
+
+ int status;
+ std::cout << "Waiting for process termination\n";
+ ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__signal_handling);
+ATF_TEST_CASE_BODY(integration__signal_handling)
+{
+ // This test scenario is racy so run it multiple times to have higher
+ // chances of exposing problems.
+ const std::size_t rounds = 20;
+
+ for (std::size_t i = 0; i < rounds; ++i) {
+ std::cout << F("Testing round %s\n") % i;
+ do_signal_handling_test(SIGHUP);
+ do_signal_handling_test(SIGINT);
+ do_signal_handling_test(SIGTERM);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__isolate_child_is_called);
+ATF_TEST_CASE_BODY(integration__isolate_child_is_called)
+{
+ executor::executor_handle handle = executor::setup();
+
+ utils::setenv("HOME", "fake-value");
+ utils::setenv("LANG", "es_ES");
+ do_spawn(handle, child_validate_isolation);
+
+ executor::exit_handle exit_handle = handle.wait_any();
+ require_exit(EXIT_SUCCESS, exit_handle.status());
+ exit_handle.cleanup();
+
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__process_group_is_terminated);
+ATF_TEST_CASE_BODY(integration__process_group_is_terminated)
+{
+ utils::setenv("CONTROL_DIR", fs::current_path().str());
+
+ executor::executor_handle handle = executor::setup();
+ do_spawn(handle, child_spawn_blocking_child);
+
+ executor::exit_handle exit_handle = handle.wait_any();
+ require_exit(EXIT_SUCCESS, exit_handle.status());
+ exit_handle.cleanup();
+
+ handle.cleanup();
+
+ if (!fs::exists(fs::path("pid")))
+ fail("The pid file was not created");
+
+ std::ifstream pidfile("pid");
+ ATF_REQUIRE(pidfile);
+ pid_t pid;
+ pidfile >> pid;
+ pidfile.close();
+
+ ensure_dead(pid);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__prevent_clobbering_control_files);
+ATF_TEST_CASE_BODY(integration__prevent_clobbering_control_files)
+{
+ executor::executor_handle handle = executor::setup();
+
+ do_spawn(handle, child_delete_all);
+
+ executor::exit_handle exit_handle = handle.wait_any();
+ require_exit(EXIT_SUCCESS, exit_handle.status());
+ ATF_REQUIRE(atf::utils::file_exists(
+ (exit_handle.control_directory() / "exec_was_called").str()));
+ ATF_REQUIRE(!atf::utils::file_exists(
+ (exit_handle.work_directory() / "exec_was_called").str()));
+ exit_handle.cleanup();
+
+ handle.cleanup();
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, integration__run_one);
+ ATF_ADD_TEST_CASE(tcs, integration__run_many);
+
+ ATF_ADD_TEST_CASE(tcs, integration__parameters_and_output);
+ ATF_ADD_TEST_CASE(tcs, integration__custom_output_files);
+ ATF_ADD_TEST_CASE(tcs, integration__timestamps);
+ ATF_ADD_TEST_CASE(tcs, integration__files);
+
+ ATF_ADD_TEST_CASE(tcs, integration__followup);
+
+ ATF_ADD_TEST_CASE(tcs, integration__output_files_always_exist);
+ ATF_ADD_TEST_CASE(tcs, integration__timeouts);
+ ATF_ADD_TEST_CASE(tcs, integration__unprivileged_user);
+ ATF_ADD_TEST_CASE(tcs, integration__auto_cleanup);
+ ATF_ADD_TEST_CASE(tcs, integration__signal_handling);
+ ATF_ADD_TEST_CASE(tcs, integration__isolate_child_is_called);
+ ATF_ADD_TEST_CASE(tcs, integration__process_group_is_terminated);
+ ATF_ADD_TEST_CASE(tcs, integration__prevent_clobbering_control_files);
+}
diff --git a/utils/process/fdstream.cpp b/utils/process/fdstream.cpp
new file mode 100644
index 000000000000..ccd7a1f91b0c
--- /dev/null
+++ b/utils/process/fdstream.cpp
@@ -0,0 +1,76 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/fdstream.hpp"
+
+#include "utils/noncopyable.hpp"
+#include "utils/process/systembuf.hpp"
+
+
+namespace utils {
+namespace process {
+
+
+/// Private implementation fields for ifdstream.
+struct ifdstream::impl : utils::noncopyable {
+ /// The systembuf backing this file descriptor.
+ systembuf _systembuf;
+
+ /// Initializes private implementation data.
+ ///
+ /// \param fd The file descriptor.
+ impl(const int fd) : _systembuf(fd) {}
+};
+
+
+} // namespace process
+} // namespace utils
+
+
+namespace process = utils::process;
+
+
+/// Constructs a new ifdstream based on an open file descriptor.
+///
+/// This grabs ownership of the file descriptor.
+///
+/// \param fd The file descriptor to read from. Must be open and valid.
+process::ifdstream::ifdstream(const int fd) :
+ std::istream(NULL),
+ _pimpl(new impl(fd))
+{
+ rdbuf(&_pimpl->_systembuf);
+}
+
+
+/// Destroys an ifdstream object.
+///
+/// \post The file descriptor attached to this stream is closed.
+process::ifdstream::~ifdstream(void)
+{
+}
diff --git a/utils/process/fdstream.hpp b/utils/process/fdstream.hpp
new file mode 100644
index 000000000000..e785b0ac4282
--- /dev/null
+++ b/utils/process/fdstream.hpp
@@ -0,0 +1,66 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/fdstream.hpp
+/// Provides the utils::process::ifdstream class.
+
+#if !defined(UTILS_PROCESS_FDSTREAM_HPP)
+#define UTILS_PROCESS_FDSTREAM_HPP
+
+#include "utils/process/fdstream_fwd.hpp"
+
+#include <istream>
+#include <memory>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace process {
+
+
+/// An input stream backed by a file descriptor.
+///
+/// This class grabs ownership of the file descriptor. I.e. when the class is
+/// destroyed, the file descriptor is closed unconditionally.
+class ifdstream : public std::istream, noncopyable
+{
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+public:
+ explicit ifdstream(const int);
+ ~ifdstream(void);
+};
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_FDSTREAM_HPP)
diff --git a/utils/process/fdstream_fwd.hpp b/utils/process/fdstream_fwd.hpp
new file mode 100644
index 000000000000..8d369ea0bfa5
--- /dev/null
+++ b/utils/process/fdstream_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/fdstream_fwd.hpp
+/// Forward declarations for utils/process/fdstream.hpp
+
+#if !defined(UTILS_PROCESS_FDSTREAM_FWD_HPP)
+#define UTILS_PROCESS_FDSTREAM_FWD_HPP
+
+namespace utils {
+namespace process {
+
+
+class ifdstream;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_FDSTREAM_FWD_HPP)
diff --git a/utils/process/fdstream_test.cpp b/utils/process/fdstream_test.cpp
new file mode 100644
index 000000000000..8420568216f0
--- /dev/null
+++ b/utils/process/fdstream_test.cpp
@@ -0,0 +1,73 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/fdstream.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include <atf-c++.hpp>
+
+#include "utils/process/systembuf.hpp"
+
+using utils::process::ifdstream;
+using utils::process::systembuf;
+
+
+ATF_TEST_CASE(ifdstream);
+ATF_TEST_CASE_HEAD(ifdstream)
+{
+ set_md_var("descr", "Tests the ifdstream class");
+}
+ATF_TEST_CASE_BODY(ifdstream)
+{
+ int fds[2];
+ ATF_REQUIRE(::pipe(fds) != -1);
+
+ ifdstream rend(fds[0]);
+
+ systembuf wbuf(fds[1]);
+ std::ostream wend(&wbuf);
+
+ // XXX This assumes that the pipe's buffer is big enough to accept
+ // the data written without blocking!
+ wend << "1Test 1message\n";
+ wend.flush();
+ std::string tmp;
+ rend >> tmp;
+ ATF_REQUIRE_EQ(tmp, "1Test");
+ rend >> tmp;
+ ATF_REQUIRE_EQ(tmp, "1message");
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, ifdstream);
+}
diff --git a/utils/process/helpers.cpp b/utils/process/helpers.cpp
new file mode 100644
index 000000000000..15deecd95f24
--- /dev/null
+++ b/utils/process/helpers.cpp
@@ -0,0 +1,74 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <sstream>
+
+
+static int
+print_args(int argc, char* argv[])
+{
+ for (int i = 0; i < argc; i++)
+ std::cout << "argv[" << i << "] = " << argv[i] << "\n";
+ std::cout << "argv[" << argc << "] = NULL";
+ return EXIT_SUCCESS;
+}
+
+
+static int
+return_code(int argc, char* argv[])
+{
+ if (argc != 3)
+ std::abort();
+
+ std::istringstream iss(argv[2]);
+ int code;
+ iss >> code;
+ return code;
+}
+
+
+int
+main(int argc, char* argv[])
+{
+ if (argc < 2) {
+ std::cerr << "Must provide a helper name\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ if (std::strcmp(argv[1], "print-args") == 0) {
+ return print_args(argc, argv);
+ } else if (std::strcmp(argv[1], "return-code") == 0) {
+ return return_code(argc, argv);
+ } else {
+ std::cerr << "Unknown helper\n";
+ return EXIT_FAILURE;
+ }
+}
diff --git a/utils/process/isolation.cpp b/utils/process/isolation.cpp
new file mode 100644
index 000000000000..90dd08d5772d
--- /dev/null
+++ b/utils/process/isolation.cpp
@@ -0,0 +1,207 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/isolation.hpp"
+
+extern "C" {
+#include <sys/stat.h>
+
+#include <grp.h>
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/env.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/misc.hpp"
+#include "utils/stacktrace.hpp"
+
+namespace fs = utils::fs;
+namespace passwd = utils::passwd;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+using utils::optional;
+
+
+/// Magic exit code to denote an error while preparing the subprocess.
+const int process::exit_isolation_failure = 124;
+
+
+namespace {
+
+
+static void fail(const std::string&, const int) UTILS_NORETURN;
+
+
+/// Fails the process with an errno-based error message.
+///
+/// \param message The message to print. The errno-based string will be
+/// appended to this, just like in perror(3).
+/// \param original_errno The error code to format.
+static void
+fail(const std::string& message, const int original_errno)
+{
+ std::cerr << message << ": " << std::strerror(original_errno) << '\n';
+ std::exit(process::exit_isolation_failure);
+}
+
+
+/// Changes the owner of a path.
+///
+/// This function is intended to be called from a subprocess getting ready to
+/// invoke an external binary. Therefore, if there is any error during the
+/// setup, the new process is terminated with an error code.
+///
+/// \param file The path to the file or directory to affect.
+/// \param uid The UID to set on the path.
+/// \param gid The GID to set on the path.
+static void
+do_chown(const fs::path& file, const uid_t uid, const gid_t gid)
+{
+ if (::chown(file.c_str(), uid, gid) == -1)
+ fail(F("chown(%s, %s, %s) failed; UID is %s and GID is %s")
+ % file % uid % gid % ::getuid() % ::getgid(), errno);
+}
+
+
+/// Resets the environment of the process to a known state.
+///
+/// \param work_directory Path to the work directory being used.
+///
+/// \throw std::runtime_error If there is a problem setting up the environment.
+static void
+prepare_environment(const fs::path& work_directory)
+{
+ const char* to_unset[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
+ "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC",
+ "LC_TIME", NULL };
+ const char** iter;
+ for (iter = to_unset; *iter != NULL; ++iter) {
+ utils::unsetenv(*iter);
+ }
+
+ utils::setenv("HOME", work_directory.str());
+ utils::setenv("TMPDIR", work_directory.str());
+ utils::setenv("TZ", "UTC");
+}
+
+
+} // anonymous namespace
+
+
+/// Cleans up the container process to run a new child.
+///
+/// If there is any error during the setup, the new process is terminated
+/// with an error code.
+///
+/// \param unprivileged_user Unprivileged user to run the test case as.
+/// \param work_directory Path to the test case-specific work directory.
+void
+process::isolate_child(const optional< passwd::user >& unprivileged_user,
+ const fs::path& work_directory)
+{
+ isolate_path(unprivileged_user, work_directory);
+ if (::chdir(work_directory.c_str()) == -1)
+ fail(F("chdir(%s) failed") % work_directory, errno);
+
+ utils::unlimit_core_size();
+ if (!signals::reset_all()) {
+ LW("Failed to reset one or more signals to their default behavior");
+ }
+ prepare_environment(work_directory);
+ (void)::umask(0022);
+
+ if (unprivileged_user && passwd::current_user().is_root()) {
+ const passwd::user& user = unprivileged_user.get();
+
+ if (user.gid != ::getgid()) {
+ if (::setgid(user.gid) == -1)
+ fail(F("setgid(%s) failed; UID is %s and GID is %s")
+ % user.gid % ::getuid() % ::getgid(), errno);
+ if (::getuid() == 0) {
+ ::gid_t groups[1];
+ groups[0] = user.gid;
+ if (::setgroups(1, groups) == -1)
+ fail(F("setgroups(1, [%s]) failed; UID is %s and GID is %s")
+ % user.gid % ::getuid() % ::getgid(), errno);
+ }
+ }
+ if (user.uid != ::getuid()) {
+ if (::setuid(user.uid) == -1)
+ fail(F("setuid(%s) failed; UID is %s and GID is %s")
+ % user.uid % ::getuid() % ::getgid(), errno);
+ }
+ }
+}
+
+
+/// Sets up a path to be writable by a child isolated with isolate_child.
+///
+/// If there is any error during the setup, the new process is terminated
+/// with an error code.
+///
+/// The caller should use this to prepare any directory or file that the child
+/// should be able to write to *before* invoking isolate_child(). Note that
+/// isolate_child() will use isolate_path() on the work directory though.
+///
+/// \param unprivileged_user Unprivileged user to run the test case as.
+/// \param file Path to the file to modify.
+void
+process::isolate_path(const optional< passwd::user >& unprivileged_user,
+ const fs::path& file)
+{
+ if (!unprivileged_user || !passwd::current_user().is_root())
+ return;
+ const passwd::user& user = unprivileged_user.get();
+
+ const bool change_group = user.gid != ::getgid();
+ const bool change_user = user.uid != ::getuid();
+
+ if (!change_user && !change_group) {
+ // Keep same permissions.
+ } else if (change_user && change_group) {
+ do_chown(file, user.uid, user.gid);
+ } else if (!change_user && change_group) {
+ do_chown(file, ::getuid(), user.gid);
+ } else {
+ INV(change_user && !change_group);
+ do_chown(file, user.uid, ::getgid());
+ }
+}
diff --git a/utils/process/isolation.hpp b/utils/process/isolation.hpp
new file mode 100644
index 000000000000..69793a76c7b4
--- /dev/null
+++ b/utils/process/isolation.hpp
@@ -0,0 +1,60 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/isolation.hpp
+/// Utilities to isolate a process.
+///
+/// By "isolation" in this context we mean forcing a process to run in a
+/// more-or-less deterministic environment.
+
+#if !defined(UTILS_PROCESS_ISOLATION_HPP)
+#define UTILS_PROCESS_ISOLATION_HPP
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+#include "utils/passwd_fwd.hpp"
+
+namespace utils {
+namespace process {
+
+
+extern const int exit_isolation_failure;
+
+
+void isolate_child(const utils::optional< utils::passwd::user >&,
+ const utils::fs::path&);
+
+void isolate_path(const utils::optional< utils::passwd::user >&,
+ const utils::fs::path&);
+
+
+} // namespace process
+} // namespace utils
+
+
+#endif // !defined(UTILS_PROCESS_ISOLATION_HPP)
diff --git a/utils/process/isolation_test.cpp b/utils/process/isolation_test.cpp
new file mode 100644
index 000000000000..dc723cc65c88
--- /dev/null
+++ b/utils/process/isolation_test.cpp
@@ -0,0 +1,622 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/isolation.hpp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/passwd.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/test_utils.ipp"
+
+namespace fs = utils::fs;
+namespace passwd = utils::passwd;
+namespace process = utils::process;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Runs the given hook in a subprocess.
+///
+/// \param hook The code to run in the subprocess.
+///
+/// \return The status of the subprocess for further validation.
+///
+/// \post The subprocess.stdout and subprocess.stderr files, created in the
+/// current directory, contain the output of the subprocess.
+template< typename Hook >
+static process::status
+fork_and_run(Hook hook)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ hook, fs::path("subprocess.stdout"), fs::path("subprocess.stderr"));
+ const process::status status = child->wait();
+
+ atf::utils::cat_file("subprocess.stdout", "isolated child stdout: ");
+ atf::utils::cat_file("subprocess.stderr", "isolated child stderr: ");
+
+ return status;
+}
+
+
+/// Subprocess that validates the cleanliness of the environment.
+///
+/// \post Exits with success if the environment is clean; failure otherwise.
+static void
+check_clean_environment(void)
+{
+ fs::mkdir(fs::path("some-directory"), 0755);
+ process::isolate_child(none, fs::path("some-directory"));
+
+ bool failed = false;
+
+ const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
+ "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC",
+ "LC_TIME", NULL };
+ const char** iter;
+ for (iter = empty; *iter != NULL; ++iter) {
+ if (utils::getenv(*iter)) {
+ failed = true;
+ std::cout << F("%s was not unset\n") % *iter;
+ }
+ }
+
+ if (utils::getenv_with_default("HOME", "") != "some-directory") {
+ failed = true;
+ std::cout << "HOME was not set to the work directory\n";
+ }
+
+ if (utils::getenv_with_default("TMPDIR", "") != "some-directory") {
+ failed = true;
+ std::cout << "TMPDIR was not set to the work directory\n";
+ }
+
+ if (utils::getenv_with_default("TZ", "") != "UTC") {
+ failed = true;
+ std::cout << "TZ was not set to UTC\n";
+ }
+
+ if (utils::getenv_with_default("LEAVE_ME_ALONE", "") != "kill-some-day") {
+ failed = true;
+ std::cout << "LEAVE_ME_ALONE was modified while it should not have "
+ "been\n";
+ }
+
+ std::exit(failed ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+
+/// Subprocess that checks if user privileges are dropped.
+class check_drop_privileges {
+ /// The user to drop the privileges to.
+ const passwd::user _unprivileged_user;
+
+public:
+ /// Constructor.
+ ///
+ /// \param unprivileged_user The user to drop the privileges to.
+ check_drop_privileges(const passwd::user& unprivileged_user) :
+ _unprivileged_user(unprivileged_user)
+ {
+ }
+
+ /// Body of the subprocess.
+ ///
+ /// \post Exits with success if the process has dropped privileges as
+ /// expected.
+ void
+ operator()(void) const
+ {
+ fs::mkdir(fs::path("subdir"), 0755);
+ process::isolate_child(utils::make_optional(_unprivileged_user),
+ fs::path("subdir"));
+
+ if (::getuid() == 0) {
+ std::cout << "UID is still 0\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ if (::getgid() == 0) {
+ std::cout << "GID is still 0\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ ::gid_t groups[1];
+ if (::getgroups(1, groups) == -1) {
+ // Should only fail if we get more than one group notifying about
+ // not enough space in the groups variable to store the whole
+ // result.
+ INV(errno == EINVAL);
+ std::exit(EXIT_FAILURE);
+ }
+ if (groups[0] == 0) {
+ std::cout << "Primary group is still 0\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ std::ofstream output("file.txt");
+ if (!output) {
+ std::cout << "Cannot write to isolated directory; owner not "
+ "changed?\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ std::exit(EXIT_SUCCESS);
+ }
+};
+
+
+/// Subprocess that dumps core to validate core dumping abilities.
+static void
+check_enable_core_dumps(void)
+{
+ process::isolate_child(none, fs::path("."));
+ std::abort();
+}
+
+
+/// Subprocess that checks if the work directory is entered.
+class check_enter_work_directory {
+ /// Directory to enter. May be releative.
+ const fs::path _directory;
+
+public:
+ /// Constructor.
+ ///
+ /// \param directory Directory to enter.
+ check_enter_work_directory(const fs::path& directory) :
+ _directory(directory)
+ {
+ }
+
+ /// Body of the subprocess.
+ ///
+ /// \post Exits with success if the process has entered the given work
+ /// directory; false otherwise.
+ void
+ operator()(void) const
+ {
+ const fs::path exp_subdir = fs::current_path() / _directory;
+ process::isolate_child(none, _directory);
+ std::exit(fs::current_path() == exp_subdir ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+ }
+};
+
+
+/// Subprocess that validates that it owns a session.
+///
+/// \post Exits with success if the process lives in its own session;
+/// failure otherwise.
+static void
+check_new_session(void)
+{
+ process::isolate_child(none, fs::path("."));
+ std::exit(::getsid(::getpid()) == ::getpid() ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+/// Subprocess that validates the disconnection from any terminal.
+///
+/// \post Exits with success if the environment is clean; failure otherwise.
+static void
+check_no_terminal(void)
+{
+ process::isolate_child(none, fs::path("."));
+
+ const char* const args[] = {
+ "/bin/sh",
+ "-i",
+ "-c",
+ "echo success",
+ NULL
+ };
+ ::execv("/bin/sh", UTILS_UNCONST(char*, args));
+ std::abort();
+}
+
+
+/// Subprocess that validates that it has become the leader of a process group.
+///
+/// \post Exits with success if the process lives in its own process group;
+/// failure otherwise.
+static void
+check_process_group(void)
+{
+ process::isolate_child(none, fs::path("."));
+ std::exit(::getpgid(::getpid()) == ::getpid() ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+/// Subprocess that validates that the umask has been reset.
+///
+/// \post Exits with success if the umask matches the expected value; failure
+/// otherwise.
+static void
+check_umask(void)
+{
+ process::isolate_child(none, fs::path("."));
+ std::exit(::umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__clean_environment);
+ATF_TEST_CASE_BODY(isolate_child__clean_environment)
+{
+ utils::setenv("HOME", "/non-existent/directory");
+ utils::setenv("TMPDIR", "/non-existent/directory");
+ utils::setenv("LANG", "C");
+ utils::setenv("LC_ALL", "C");
+ utils::setenv("LC_COLLATE", "C");
+ utils::setenv("LC_CTYPE", "C");
+ utils::setenv("LC_MESSAGES", "C");
+ utils::setenv("LC_MONETARY", "C");
+ utils::setenv("LC_NUMERIC", "C");
+ utils::setenv("LC_TIME", "C");
+ utils::setenv("LEAVE_ME_ALONE", "kill-some-day");
+ utils::setenv("TZ", "EST+5");
+
+ const process::status status = fork_and_run(check_clean_environment);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE(isolate_child__other_user_when_unprivileged);
+ATF_TEST_CASE_HEAD(isolate_child__other_user_when_unprivileged)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(isolate_child__other_user_when_unprivileged)
+{
+ const passwd::user user = passwd::current_user();
+
+ passwd::user other_user = user;
+ other_user.uid += 1;
+ other_user.gid += 1;
+ process::isolate_child(utils::make_optional(other_user), fs::path("."));
+
+ ATF_REQUIRE_EQ(user.uid, ::getuid());
+ ATF_REQUIRE_EQ(user.gid, ::getgid());
+}
+
+
+ATF_TEST_CASE(isolate_child__drop_privileges);
+ATF_TEST_CASE_HEAD(isolate_child__drop_privileges)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(isolate_child__drop_privileges)
+{
+ const passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+
+ const process::status status = fork_and_run(check_drop_privileges(
+ unprivileged_user));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE(isolate_child__drop_privileges_fail_uid);
+ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_uid)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_uid)
+{
+ // Fake the current user as root so that we bypass the protections in
+ // isolate_child that prevent us from attempting a user switch when we are
+ // not root. We do this so we can trigger the setuid failure.
+ passwd::user root = passwd::user("root", 0, 0);
+ ATF_REQUIRE(root.is_root());
+ passwd::set_current_user_for_testing(root);
+
+ passwd::user unprivileged_user = passwd::current_user();
+ unprivileged_user.uid += 1;
+
+ const process::status status = fork_and_run(check_drop_privileges(
+ unprivileged_user));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("(chown|setuid).*failed",
+ "subprocess.stderr"));
+}
+
+
+ATF_TEST_CASE(isolate_child__drop_privileges_fail_gid);
+ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_gid)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_gid)
+{
+ // Fake the current user as root so that we bypass the protections in
+ // isolate_child that prevent us from attempting a user switch when we are
+ // not root. We do this so we can trigger the setgid failure.
+ passwd::user root = passwd::user("root", 0, 0);
+ ATF_REQUIRE(root.is_root());
+ passwd::set_current_user_for_testing(root);
+
+ passwd::user unprivileged_user = passwd::current_user();
+ unprivileged_user.gid += 1;
+
+ const process::status status = fork_and_run(check_drop_privileges(
+ unprivileged_user));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("(chown|setgid).*failed",
+ "subprocess.stderr"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enable_core_dumps);
+ATF_TEST_CASE_BODY(isolate_child__enable_core_dumps)
+{
+ utils::require_run_coredump_tests(this);
+
+ struct ::rlimit rl;
+ if (::getrlimit(RLIMIT_CORE, &rl) == -1)
+ fail("Failed to query the core size limit");
+ if (rl.rlim_cur == 0 || rl.rlim_max == 0)
+ skip("Maximum core size is zero; cannot run test");
+ rl.rlim_cur = 0;
+ if (::setrlimit(RLIMIT_CORE, &rl) == -1)
+ fail("Failed to lower the core size limit");
+
+ const process::status status = fork_and_run(check_enable_core_dumps);
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(status.coredump());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory);
+ATF_TEST_CASE_BODY(isolate_child__enter_work_directory)
+{
+ const fs::path directory("some/sub/directory");
+ fs::mkdir_p(directory, 0755);
+ const process::status status = fork_and_run(
+ check_enter_work_directory(directory));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory_failure);
+ATF_TEST_CASE_BODY(isolate_child__enter_work_directory_failure)
+{
+ const fs::path directory("some/sub/directory");
+ const process::status status = fork_and_run(
+ check_enter_work_directory(directory));
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("chdir\\(some/sub/directory\\) failed",
+ "subprocess.stderr"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__new_session);
+ATF_TEST_CASE_BODY(isolate_child__new_session)
+{
+ const process::status status = fork_and_run(check_new_session);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__no_terminal);
+ATF_TEST_CASE_BODY(isolate_child__no_terminal)
+{
+ const process::status status = fork_and_run(check_no_terminal);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__process_group);
+ATF_TEST_CASE_BODY(isolate_child__process_group)
+{
+ const process::status status = fork_and_run(check_process_group);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__reset_umask);
+ATF_TEST_CASE_BODY(isolate_child__reset_umask)
+{
+ const process::status status = fork_and_run(check_umask);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+/// Executes isolate_path() and compares the on-disk changes to expected values.
+///
+/// \param unprivileged_user The user to pass to isolate_path; may be none.
+/// \param exp_uid Expected UID or none to expect the old value.
+/// \param exp_gid Expected GID or none to expect the old value.
+static void
+do_isolate_path_test(const optional< passwd::user >& unprivileged_user,
+ const optional< uid_t >& exp_uid,
+ const optional< gid_t >& exp_gid)
+{
+ const fs::path dir("dir");
+ fs::mkdir(dir, 0755);
+ struct ::stat old_sb;
+ ATF_REQUIRE(::stat(dir.c_str(), &old_sb) != -1);
+
+ process::isolate_path(unprivileged_user, dir);
+
+ struct ::stat new_sb;
+ ATF_REQUIRE(::stat(dir.c_str(), &new_sb) != -1);
+
+ if (exp_uid)
+ ATF_REQUIRE_EQ(exp_uid.get(), new_sb.st_uid);
+ else
+ ATF_REQUIRE_EQ(old_sb.st_uid, new_sb.st_uid);
+
+ if (exp_gid)
+ ATF_REQUIRE_EQ(exp_gid.get(), new_sb.st_gid);
+ else
+ ATF_REQUIRE_EQ(old_sb.st_gid, new_sb.st_gid);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__no_user);
+ATF_TEST_CASE_BODY(isolate_path__no_user)
+{
+ do_isolate_path_test(none, none, none);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__same_user);
+ATF_TEST_CASE_BODY(isolate_path__same_user)
+{
+ do_isolate_path_test(utils::make_optional(passwd::current_user()),
+ none, none);
+}
+
+
+ATF_TEST_CASE(isolate_path__other_user_when_unprivileged);
+ATF_TEST_CASE_HEAD(isolate_path__other_user_when_unprivileged)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(isolate_path__other_user_when_unprivileged)
+{
+ passwd::user user = passwd::current_user();
+ user.uid += 1;
+ user.gid += 1;
+
+ do_isolate_path_test(utils::make_optional(user), none, none);
+}
+
+
+ATF_TEST_CASE(isolate_path__drop_privileges);
+ATF_TEST_CASE_HEAD(isolate_path__drop_privileges)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(isolate_path__drop_privileges)
+{
+ const passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+ do_isolate_path_test(utils::make_optional(unprivileged_user),
+ utils::make_optional(unprivileged_user.uid),
+ utils::make_optional(unprivileged_user.gid));
+}
+
+
+ATF_TEST_CASE(isolate_path__drop_privileges_only_uid);
+ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_uid)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_uid)
+{
+ passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+ unprivileged_user.gid = ::getgid();
+ do_isolate_path_test(utils::make_optional(unprivileged_user),
+ utils::make_optional(unprivileged_user.uid),
+ none);
+}
+
+
+ATF_TEST_CASE(isolate_path__drop_privileges_only_gid);
+ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_gid)
+{
+ set_md_var("require.config", "unprivileged-user");
+ set_md_var("require.user", "root");
+}
+ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_gid)
+{
+ passwd::user unprivileged_user = passwd::find_user_by_name(
+ get_config_var("unprivileged-user"));
+ unprivileged_user.uid = ::getuid();
+ do_isolate_path_test(utils::make_optional(unprivileged_user),
+ none,
+ utils::make_optional(unprivileged_user.gid));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, isolate_child__clean_environment);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__other_user_when_unprivileged);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_uid);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_gid);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__enable_core_dumps);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory_failure);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__new_session);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__no_terminal);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__process_group);
+ ATF_ADD_TEST_CASE(tcs, isolate_child__reset_umask);
+
+ ATF_ADD_TEST_CASE(tcs, isolate_path__no_user);
+ ATF_ADD_TEST_CASE(tcs, isolate_path__same_user);
+ ATF_ADD_TEST_CASE(tcs, isolate_path__other_user_when_unprivileged);
+ ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges);
+ ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_uid);
+ ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_gid);
+}
diff --git a/utils/process/operations.cpp b/utils/process/operations.cpp
new file mode 100644
index 000000000000..abcc49f2a443
--- /dev/null
+++ b/utils/process/operations.cpp
@@ -0,0 +1,273 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/operations.hpp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/process/exceptions.hpp"
+#include "utils/process/system.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/interrupts.hpp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+/// Maximum number of arguments supported by exec.
+///
+/// We need this limit to avoid having to allocate dynamic memory in the child
+/// process to construct the arguments list, which would have side-effects in
+/// the parent's memory if we use vfork().
+#define MAX_ARGS 128
+
+
+namespace {
+
+
+/// Exception-based, type-improved version of wait(2).
+///
+/// \return The PID of the terminated process and its termination status.
+///
+/// \throw process::system_error If the call to wait(2) fails.
+static process::status
+safe_wait(void)
+{
+ LD("Waiting for any child process");
+ int stat_loc;
+ const pid_t pid = ::wait(&stat_loc);
+ if (pid == -1) {
+ const int original_errno = errno;
+ throw process::system_error("Failed to wait for any child process",
+ original_errno);
+ }
+ return process::status(pid, stat_loc);
+}
+
+
+/// Exception-based, type-improved version of waitpid(2).
+///
+/// \param pid The identifier of the process to wait for.
+///
+/// \return The termination status of the process.
+///
+/// \throw process::system_error If the call to waitpid(2) fails.
+static process::status
+safe_waitpid(const pid_t pid)
+{
+ LD(F("Waiting for pid=%s") % pid);
+ int stat_loc;
+ if (process::detail::syscall_waitpid(pid, &stat_loc, 0) == -1) {
+ const int original_errno = errno;
+ throw process::system_error(F("Failed to wait for PID %s") % pid,
+ original_errno);
+ }
+ return process::status(pid, stat_loc);
+}
+
+
+} // anonymous namespace
+
+
+/// Executes an external binary and replaces the current process.
+///
+/// This function must not use any of the logging features so that the output
+/// of the subprocess is not "polluted" by our own messages.
+///
+/// This function must also not affect the global state of the current process
+/// as otherwise we would not be able to use vfork(). Only state stored in the
+/// stack can be touched.
+///
+/// \param program The binary to execute.
+/// \param args The arguments to pass to the binary, without the program name.
+void
+process::exec(const fs::path& program, const args_vector& args) throw()
+{
+ try {
+ exec_unsafe(program, args);
+ } catch (const system_error& error) {
+ // Error message already printed by exec_unsafe.
+ std::abort();
+ }
+}
+
+
+/// Executes an external binary and replaces the current process.
+///
+/// This differs from process::exec() in that this function reports errors
+/// caused by the exec(2) system call to let the caller decide how to handle
+/// them.
+///
+/// This function must not use any of the logging features so that the output
+/// of the subprocess is not "polluted" by our own messages.
+///
+/// This function must also not affect the global state of the current process
+/// as otherwise we would not be able to use vfork(). Only state stored in the
+/// stack can be touched.
+///
+/// \param program The binary to execute.
+/// \param args The arguments to pass to the binary, without the program name.
+///
+/// \throw system_error If the exec(2) call fails.
+void
+process::exec_unsafe(const fs::path& program, const args_vector& args)
+{
+ PRE(args.size() < MAX_ARGS);
+ int original_errno = 0;
+ try {
+ const char* argv[MAX_ARGS + 1];
+
+ argv[0] = program.c_str();
+ for (args_vector::size_type i = 0; i < args.size(); i++)
+ argv[1 + i] = args[i].c_str();
+ argv[1 + args.size()] = NULL;
+
+ const int ret = ::execv(program.c_str(),
+ (char* const*)(unsigned long)(const void*)argv);
+ original_errno = errno;
+ INV(ret == -1);
+ std::cerr << "Failed to execute " << program << ": "
+ << std::strerror(original_errno) << "\n";
+ } catch (const std::runtime_error& error) {
+ std::cerr << "Failed to execute " << program << ": "
+ << error.what() << "\n";
+ std::abort();
+ } catch (...) {
+ std::cerr << "Failed to execute " << program << "; got unexpected "
+ "exception during exec\n";
+ std::abort();
+ }
+
+ // We must do this here to prevent our exception from being caught by the
+ // generic handlers above.
+ INV(original_errno != 0);
+ throw system_error("Failed to execute " + program.str(), original_errno);
+}
+
+
+/// Forcibly kills a process group started by us.
+///
+/// This function is safe to call from an signal handler context.
+///
+/// Pretty much all of our subprocesses run in their own process group so that
+/// we can terminate them and thier children should we need to. Because of
+/// this, the very first thing our subprocesses do is create a new process group
+/// for themselves.
+///
+/// The implication of the above is that simply issuing a killpg() call on the
+/// process group is racy: if the subprocess has not yet had a chance to prepare
+/// its own process group, then we will not be killing anything. To solve this,
+/// we must also kill() the process group leader itself, and we must do so after
+/// the call to killpg(). Doing this is safe because: 1) the process group must
+/// have the same ID as the PID of the process that created it; and 2) we have
+/// not yet issued a wait() call so we still own the PID.
+///
+/// The sideffect of doing what we do here is that the process group leader may
+/// receive a signal twice. But we don't care because we are forcibly
+/// terminating the process group and none of the processes can controlledly
+/// react to SIGKILL.
+///
+/// \param pgid PID or process group ID to terminate.
+void
+process::terminate_group(const int pgid)
+{
+ (void)::killpg(pgid, SIGKILL);
+ (void)::kill(pgid, SIGKILL);
+}
+
+
+/// Terminates the current process reproducing the given status.
+///
+/// The caller process is abruptly terminated. In particular, no output streams
+/// are flushed, no destructors are called, and no atexit(2) handlers are run.
+///
+/// \param status The status to "re-deliver" to the caller process.
+void
+process::terminate_self_with(const status& status)
+{
+ if (status.exited()) {
+ ::_exit(status.exitstatus());
+ } else {
+ INV(status.signaled());
+ (void)::kill(::getpid(), status.termsig());
+ UNREACHABLE_MSG(F("Signal %s terminated %s but did not terminate "
+ "ourselves") % status.termsig() % status.dead_pid());
+ }
+}
+
+
+/// Blocks to wait for completion of a subprocess.
+///
+/// \param pid Identifier of the process to wait for.
+///
+/// \return The termination status of the child process that terminated.
+///
+/// \throw process::system_error If the call to wait(2) fails.
+process::status
+process::wait(const int pid)
+{
+ const process::status status = safe_waitpid(pid);
+ {
+ signals::interrupts_inhibiter inhibiter;
+ signals::remove_pid_to_kill(pid);
+ }
+ return status;
+}
+
+
+/// Blocks to wait for completion of any subprocess.
+///
+/// \return The termination status of the child process that terminated.
+///
+/// \throw process::system_error If the call to wait(2) fails.
+process::status
+process::wait_any(void)
+{
+ const process::status status = safe_wait();
+ {
+ signals::interrupts_inhibiter inhibiter;
+ signals::remove_pid_to_kill(status.dead_pid());
+ }
+ return status;
+}
diff --git a/utils/process/operations.hpp b/utils/process/operations.hpp
new file mode 100644
index 000000000000..773f9d38bb74
--- /dev/null
+++ b/utils/process/operations.hpp
@@ -0,0 +1,56 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/operations.hpp
+/// Collection of utilities for process management.
+
+#if !defined(UTILS_PROCESS_OPERATIONS_HPP)
+#define UTILS_PROCESS_OPERATIONS_HPP
+
+#include "utils/process/operations_fwd.hpp"
+
+#include "utils/defs.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/process/status_fwd.hpp"
+
+namespace utils {
+namespace process {
+
+
+void exec(const utils::fs::path&, const args_vector&) throw() UTILS_NORETURN;
+void exec_unsafe(const utils::fs::path&, const args_vector&) UTILS_NORETURN;
+void terminate_group(const int);
+void terminate_self_with(const status&) UTILS_NORETURN;
+status wait(const int);
+status wait_any(void);
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_OPERATIONS_HPP)
diff --git a/utils/process/operations_fwd.hpp b/utils/process/operations_fwd.hpp
new file mode 100644
index 000000000000..bd23fdc2c691
--- /dev/null
+++ b/utils/process/operations_fwd.hpp
@@ -0,0 +1,49 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/operations_fwd.hpp
+/// Forward declarations for utils/process/operations.hpp
+
+#if !defined(UTILS_PROCESS_OPERATIONS_FWD_HPP)
+#define UTILS_PROCESS_OPERATIONS_FWD_HPP
+
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace process {
+
+
+/// Arguments to a program, without the program name.
+typedef std::vector< std::string > args_vector;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_OPERATIONS_FWD_HPP)
diff --git a/utils/process/operations_test.cpp b/utils/process/operations_test.cpp
new file mode 100644
index 000000000000..e9c1ebb65a3d
--- /dev/null
+++ b/utils/process/operations_test.cpp
@@ -0,0 +1,471 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/operations.hpp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/format/containers.ipp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/exceptions.hpp"
+#include "utils/process/status.hpp"
+#include "utils/stacktrace.hpp"
+#include "utils/test_utils.ipp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// Type of the process::exec() and process::exec_unsafe() functions.
+typedef void (*exec_function)(const fs::path&, const process::args_vector&);
+
+
+/// Calculates the path to the test helpers binary.
+///
+/// \param tc A pointer to the caller test case, needed to extract the value of
+/// the "srcdir" property.
+///
+/// \return The path to the helpers binary.
+static fs::path
+get_helpers(const atf::tests::tc* tc)
+{
+ return fs::path(tc->get_config_var("srcdir")) / "helpers";
+}
+
+
+/// Body for a subprocess that runs exec().
+class child_exec {
+ /// Function to do the exec.
+ const exec_function _do_exec;
+
+ /// Path to the binary to exec.
+ const fs::path& _program;
+
+ /// Arguments to the binary, not including argv[0].
+ const process::args_vector& _args;
+
+public:
+ /// Constructor.
+ ///
+ /// \param do_exec Function to do the exec.
+ /// \param program Path to the binary to exec.
+ /// \param args Arguments to the binary, not including argv[0].
+ child_exec(const exec_function do_exec, const fs::path& program,
+ const process::args_vector& args) :
+ _do_exec(do_exec), _program(program), _args(args)
+ {
+ }
+
+ /// Body for the subprocess.
+ void
+ operator()(void)
+ {
+ _do_exec(_program, _args);
+ }
+};
+
+
+/// Body for a process that returns a specific exit code.
+///
+/// \tparam ExitStatus The exit status for the subprocess.
+template< int ExitStatus >
+static void
+child_exit(void)
+{
+ std::exit(ExitStatus);
+}
+
+
+static void suspend(void) UTILS_NORETURN;
+
+
+/// Blocks a subprocess from running indefinitely.
+static void
+suspend(void)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ for (;;) {
+ ::sigsuspend(&mask);
+ }
+}
+
+
+static void write_loop(const int) UTILS_NORETURN;
+
+
+/// Provides an infinite stream of data in a subprocess.
+///
+/// \param fd Descriptor into which to write.
+static void
+write_loop(const int fd)
+{
+ const int cookie = 0x12345678;
+ for (;;) {
+ std::cerr << "Still alive in PID " << ::getpid() << '\n';
+ if (::write(fd, &cookie, sizeof(cookie)) != sizeof(cookie))
+ std::exit(EXIT_FAILURE);
+ ::sleep(1);
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Tests an exec function with no arguments.
+///
+/// \param tc The calling test case.
+/// \param do_exec The exec function to test.
+static void
+check_exec_no_args(const atf::tests::tc* tc, const exec_function do_exec)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_exec(do_exec, get_helpers(tc), process::args_vector()),
+ fs::path("stdout"), fs::path("stderr"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("Must provide a helper name", "stderr"));
+}
+
+
+/// Tests an exec function with some arguments.
+///
+/// \param tc The calling test case.
+/// \param do_exec The exec function to test.
+static void
+check_exec_some_args(const atf::tests::tc* tc, const exec_function do_exec)
+{
+ process::args_vector args;
+ args.push_back("print-args");
+ args.push_back("foo");
+ args.push_back("bar");
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_exec(do_exec, get_helpers(tc), args),
+ fs::path("stdout"), fs::path("stderr"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("argv\\[1\\] = print-args", "stdout"));
+ ATF_REQUIRE(atf::utils::grep_file("argv\\[2\\] = foo", "stdout"));
+ ATF_REQUIRE(atf::utils::grep_file("argv\\[3\\] = bar", "stdout"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__no_args);
+ATF_TEST_CASE_BODY(exec__no_args)
+{
+ check_exec_no_args(this, process::exec);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__some_args);
+ATF_TEST_CASE_BODY(exec__some_args)
+{
+ check_exec_some_args(this, process::exec);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__fail);
+ATF_TEST_CASE_BODY(exec__fail)
+{
+ utils::avoid_coredump_on_crash();
+
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ child_exec(process::exec, fs::path("non-existent"),
+ process::args_vector()),
+ fs::path("stdout"), fs::path("stderr"));
+ const process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(atf::utils::grep_file("Failed to execute non-existent",
+ "stderr"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__no_args);
+ATF_TEST_CASE_BODY(exec_unsafe__no_args)
+{
+ check_exec_no_args(this, process::exec_unsafe);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__some_args);
+ATF_TEST_CASE_BODY(exec_unsafe__some_args)
+{
+ check_exec_some_args(this, process::exec_unsafe);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__fail);
+ATF_TEST_CASE_BODY(exec_unsafe__fail)
+{
+ ATF_REQUIRE_THROW_RE(
+ process::system_error, "Failed to execute missing-program",
+ process::exec_unsafe(fs::path("missing-program"),
+ process::args_vector()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_executed);
+ATF_TEST_CASE_BODY(terminate_group__setpgrp_executed)
+{
+ int first_fds[2], second_fds[2];
+ ATF_REQUIRE(::pipe(first_fds) != -1);
+ ATF_REQUIRE(::pipe(second_fds) != -1);
+
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ ::setpgid(::getpid(), ::getpid());
+ const pid_t pid2 = ::fork();
+ if (pid2 == -1) {
+ std::exit(EXIT_FAILURE);
+ } else if (pid2 == 0) {
+ ::close(first_fds[0]);
+ ::close(first_fds[1]);
+ ::close(second_fds[0]);
+ write_loop(second_fds[1]);
+ }
+ ::close(first_fds[0]);
+ ::close(second_fds[0]);
+ ::close(second_fds[1]);
+ write_loop(first_fds[1]);
+ }
+ ::close(first_fds[1]);
+ ::close(second_fds[1]);
+
+ int dummy;
+ std::cerr << "Waiting for children to start\n";
+ while (::read(first_fds[0], &dummy, sizeof(dummy)) <= 0 ||
+ ::read(second_fds[0], &dummy, sizeof(dummy)) <= 0) {
+ // Wait for children to come up.
+ }
+
+ process::terminate_group(pid);
+ std::cerr << "Waiting for children to die\n";
+ while (::read(first_fds[0], &dummy, sizeof(dummy)) > 0 ||
+ ::read(second_fds[0], &dummy, sizeof(dummy)) > 0) {
+ // Wait for children to terminate. If they don't, then the test case
+ // will time out.
+ }
+
+ int status;
+ ATF_REQUIRE(::wait(&status) != -1);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_not_executed);
+ATF_TEST_CASE_BODY(terminate_group__setpgrp_not_executed)
+{
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ // We do not call setgprp() here to simulate the race that happens when
+ // we invoke terminate_group on a process that has not yet had a chance
+ // to run the setpgrp() call.
+ suspend();
+ }
+
+ process::terminate_group(pid);
+
+ int status;
+ ATF_REQUIRE(::wait(&status) != -1);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__exitstatus);
+ATF_TEST_CASE_BODY(terminate_self_with__exitstatus)
+{
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ const process::status status = process::status::fake_exited(123);
+ process::terminate_self_with(status);
+ }
+
+ int status;
+ ATF_REQUIRE(::wait(&status) != -1);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 123);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig);
+ATF_TEST_CASE_BODY(terminate_self_with__termsig)
+{
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ const process::status status = process::status::fake_signaled(
+ SIGKILL, false);
+ process::terminate_self_with(status);
+ }
+
+ int status;
+ ATF_REQUIRE(::wait(&status) != -1);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+ ATF_REQUIRE(!WCOREDUMP(status));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig_and_core);
+ATF_TEST_CASE_BODY(terminate_self_with__termsig_and_core)
+{
+ utils::prepare_coredump_test(this);
+
+ const pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ const process::status status = process::status::fake_signaled(
+ SIGABRT, true);
+ process::terminate_self_with(status);
+ }
+
+ int status;
+ ATF_REQUIRE(::wait(&status) != -1);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGABRT);
+ ATF_REQUIRE(WCOREDUMP(status));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(wait__ok);
+ATF_TEST_CASE_BODY(wait__ok)
+{
+ std::auto_ptr< process::child > child = process::child::fork_capture(
+ child_exit< 15 >);
+ const pid_t pid = child->pid();
+ child.reset(); // Ensure there is no conflict between destructor and wait.
+
+ const process::status status = process::wait(pid);
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(15, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(wait__fail);
+ATF_TEST_CASE_BODY(wait__fail)
+{
+ ATF_REQUIRE_THROW(process::system_error, process::wait(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(wait_any__one);
+ATF_TEST_CASE_BODY(wait_any__one)
+{
+ process::child::fork_capture(child_exit< 15 >);
+
+ const process::status status = process::wait_any();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(15, status.exitstatus());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(wait_any__many);
+ATF_TEST_CASE_BODY(wait_any__many)
+{
+ process::child::fork_capture(child_exit< 15 >);
+ process::child::fork_capture(child_exit< 30 >);
+ process::child::fork_capture(child_exit< 45 >);
+
+ std::set< int > exit_codes;
+ for (int i = 0; i < 3; i++) {
+ const process::status status = process::wait_any();
+ ATF_REQUIRE(status.exited());
+ exit_codes.insert(status.exitstatus());
+ }
+
+ std::set< int > exp_exit_codes;
+ exp_exit_codes.insert(15);
+ exp_exit_codes.insert(30);
+ exp_exit_codes.insert(45);
+ ATF_REQUIRE_EQ(exp_exit_codes, exit_codes);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(wait_any__none_is_failure);
+ATF_TEST_CASE_BODY(wait_any__none_is_failure)
+{
+ try {
+ const process::status status = process::wait_any();
+ fail("Expected exception but none raised");
+ } catch (const process::system_error& e) {
+ ATF_REQUIRE(atf::utils::grep_string("Failed to wait", e.what()));
+ ATF_REQUIRE_EQ(ECHILD, e.original_errno());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, exec__no_args);
+ ATF_ADD_TEST_CASE(tcs, exec__some_args);
+ ATF_ADD_TEST_CASE(tcs, exec__fail);
+
+ ATF_ADD_TEST_CASE(tcs, exec_unsafe__no_args);
+ ATF_ADD_TEST_CASE(tcs, exec_unsafe__some_args);
+ ATF_ADD_TEST_CASE(tcs, exec_unsafe__fail);
+
+ ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_executed);
+ ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_not_executed);
+
+ ATF_ADD_TEST_CASE(tcs, terminate_self_with__exitstatus);
+ ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig);
+ ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig_and_core);
+
+ ATF_ADD_TEST_CASE(tcs, wait__ok);
+ ATF_ADD_TEST_CASE(tcs, wait__fail);
+
+ ATF_ADD_TEST_CASE(tcs, wait_any__one);
+ ATF_ADD_TEST_CASE(tcs, wait_any__many);
+ ATF_ADD_TEST_CASE(tcs, wait_any__none_is_failure);
+}
diff --git a/utils/process/status.cpp b/utils/process/status.cpp
new file mode 100644
index 000000000000..a3cea8e09ebd
--- /dev/null
+++ b/utils/process/status.cpp
@@ -0,0 +1,200 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/status.hpp"
+
+extern "C" {
+#include <sys/wait.h>
+}
+
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+
+namespace process = utils::process;
+
+using utils::none;
+using utils::optional;
+
+#if !defined(WCOREDUMP)
+# define WCOREDUMP(x) false
+#endif
+
+
+/// Constructs a new status object based on the status value of waitpid(2).
+///
+/// \param dead_pid_ The PID of the process this status belonged to.
+/// \param stat_loc The status value returnd by waitpid(2).
+process::status::status(const int dead_pid_, int stat_loc) :
+ _dead_pid(dead_pid_),
+ _exited(WIFEXITED(stat_loc) ?
+ optional< int >(WEXITSTATUS(stat_loc)) : none),
+ _signaled(WIFSIGNALED(stat_loc) ?
+ optional< std::pair< int, bool > >(
+ std::make_pair(WTERMSIG(stat_loc), WCOREDUMP(stat_loc))) :
+ none)
+{
+}
+
+
+/// Constructs a new status object based on fake values.
+///
+/// \param exited_ If not none, specifies the exit status of the program.
+/// \param signaled_ If not none, specifies the termination signal and whether
+/// the process dumped core or not.
+process::status::status(const optional< int >& exited_,
+ const optional< std::pair< int, bool > >& signaled_) :
+ _dead_pid(-1),
+ _exited(exited_),
+ _signaled(signaled_)
+{
+}
+
+
+/// Constructs a new status object based on a fake exit status.
+///
+/// \param exitstatus_ The exit code of the process.
+///
+/// \return A status object with fake data.
+process::status
+process::status::fake_exited(const int exitstatus_)
+{
+ return status(utils::make_optional(exitstatus_), none);
+}
+
+
+/// Constructs a new status object based on a fake exit status.
+///
+/// \param termsig_ The termination signal of the process.
+/// \param coredump_ Whether the process dumped core or not.
+///
+/// \return A status object with fake data.
+process::status
+process::status::fake_signaled(const int termsig_, const bool coredump_)
+{
+ return status(none, utils::make_optional(std::make_pair(termsig_,
+ coredump_)));
+}
+
+
+/// Returns the PID of the process this status was taken from.
+///
+/// Please note that the process is already dead and gone from the system. This
+/// PID can only be used for informational reasons and not to address the
+/// process in any way.
+///
+/// \return The PID of the original process.
+int
+process::status::dead_pid(void) const
+{
+ return _dead_pid;
+}
+
+
+/// Returns whether the process exited cleanly or not.
+///
+/// \return True if the process exited cleanly, false otherwise.
+bool
+process::status::exited(void) const
+{
+ return _exited;
+}
+
+
+/// Returns the exit code of the process.
+///
+/// \pre The process must have exited cleanly (i.e. exited() must be true).
+///
+/// \return The exit code.
+int
+process::status::exitstatus(void) const
+{
+ PRE(exited());
+ return _exited.get();
+}
+
+
+/// Returns whether the process terminated due to a signal or not.
+///
+/// \return True if the process terminated due to a signal, false otherwise.
+bool
+process::status::signaled(void) const
+{
+ return _signaled;
+}
+
+
+/// Returns the signal that terminated the process.
+///
+/// \pre The process must have terminated by a signal (i.e. signaled() must be
+/// true.
+///
+/// \return The signal number.
+int
+process::status::termsig(void) const
+{
+ PRE(signaled());
+ return _signaled.get().first;
+}
+
+
+/// Returns whether the process core dumped or not.
+///
+/// This functionality may be unsupported in some platforms. In such cases,
+/// this method returns false unconditionally.
+///
+/// \pre The process must have terminated by a signal (i.e. signaled() must be
+/// true.
+///
+/// \return True if the process dumped core, false otherwise.
+bool
+process::status::coredump(void) const
+{
+ PRE(signaled());
+ return _signaled.get().second;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param status The object to format.
+///
+/// \return The output stream.
+std::ostream&
+process::operator<<(std::ostream& output, const status& status)
+{
+ if (status.exited()) {
+ output << F("status{exitstatus=%s}") % status.exitstatus();
+ } else {
+ INV(status.signaled());
+ output << F("status{termsig=%s, coredump=%s}") % status.termsig() %
+ status.coredump();
+ }
+ return output;
+}
diff --git a/utils/process/status.hpp b/utils/process/status.hpp
new file mode 100644
index 000000000000..b14ff55c01a2
--- /dev/null
+++ b/utils/process/status.hpp
@@ -0,0 +1,84 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/status.hpp
+/// Provides the utils::process::status class.
+
+#if !defined(UTILS_PROCESS_STATUS_HPP)
+#define UTILS_PROCESS_STATUS_HPP
+
+#include "utils/process/status_fwd.hpp"
+
+#include <ostream>
+#include <utility>
+
+#include "utils/optional.ipp"
+
+namespace utils {
+namespace process {
+
+
+/// Representation of the termination status of a process.
+class status {
+ /// The PID of the process that generated this status.
+ ///
+ /// Note that the process has exited already and been awaited for, so the
+ /// PID cannot be used to address the process.
+ int _dead_pid;
+
+ /// The exit status of the process, if it exited cleanly.
+ optional< int > _exited;
+
+ /// The signal that terminated the program, if any, and if it dumped core.
+ optional< std::pair< int, bool > > _signaled;
+
+ status(const optional< int >&, const optional< std::pair< int, bool > >&);
+
+public:
+ status(const int, int);
+ static status fake_exited(const int);
+ static status fake_signaled(const int, const bool);
+
+ int dead_pid(void) const;
+
+ bool exited(void) const;
+ int exitstatus(void) const;
+
+ bool signaled(void) const;
+ int termsig(void) const;
+ bool coredump(void) const;
+};
+
+
+std::ostream& operator<<(std::ostream&, const status&);
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_STATUS_HPP)
diff --git a/utils/process/status_fwd.hpp b/utils/process/status_fwd.hpp
new file mode 100644
index 000000000000..3a14683dc15c
--- /dev/null
+++ b/utils/process/status_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/status_fwd.hpp
+/// Forward declarations for utils/process/status.hpp
+
+#if !defined(UTILS_PROCESS_STATUS_FWD_HPP)
+#define UTILS_PROCESS_STATUS_FWD_HPP
+
+namespace utils {
+namespace process {
+
+
+class status;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_STATUS_FWD_HPP)
diff --git a/utils/process/status_test.cpp b/utils/process/status_test.cpp
new file mode 100644
index 000000000000..5a3e19eeaf18
--- /dev/null
+++ b/utils/process/status_test.cpp
@@ -0,0 +1,209 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/status.hpp"
+
+extern "C" {
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+
+#include <atf-c++.hpp>
+
+#include "utils/test_utils.ipp"
+
+using utils::process::status;
+
+
+namespace {
+
+
+/// Body of a subprocess that exits with a particular exit status.
+///
+/// \tparam ExitStatus The status to exit with.
+template< int ExitStatus >
+void child_exit(void)
+{
+ std::exit(ExitStatus);
+}
+
+
+/// Body of a subprocess that sends a particular signal to itself.
+///
+/// \tparam Signo The signal to send to self.
+template< int Signo >
+void child_signal(void)
+{
+ ::kill(::getpid(), Signo);
+}
+
+
+/// Spawns a process and waits for completion.
+///
+/// \param hook The function to run within the child. Should not return.
+///
+/// \return The termination status of the spawned subprocess.
+status
+fork_and_wait(void (*hook)(void))
+{
+ pid_t pid = ::fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0) {
+ hook();
+ std::abort();
+ } else {
+ int stat_loc;
+ ATF_REQUIRE(::waitpid(pid, &stat_loc, 0) != -1);
+ const status s = status(pid, stat_loc);
+ ATF_REQUIRE_EQ(pid, s.dead_pid());
+ return s;
+ }
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(fake_exited)
+ATF_TEST_CASE_BODY(fake_exited)
+{
+ const status fake = status::fake_exited(123);
+ ATF_REQUIRE_EQ(-1, fake.dead_pid());
+ ATF_REQUIRE(fake.exited());
+ ATF_REQUIRE_EQ(123, fake.exitstatus());
+ ATF_REQUIRE(!fake.signaled());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(fake_signaled)
+ATF_TEST_CASE_BODY(fake_signaled)
+{
+ const status fake = status::fake_signaled(567, true);
+ ATF_REQUIRE_EQ(-1, fake.dead_pid());
+ ATF_REQUIRE(!fake.exited());
+ ATF_REQUIRE(fake.signaled());
+ ATF_REQUIRE_EQ(567, fake.termsig());
+ ATF_REQUIRE(fake.coredump());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(output__exitstatus);
+ATF_TEST_CASE_BODY(output__exitstatus)
+{
+ const status fake = status::fake_exited(123);
+ std::ostringstream str;
+ str << fake;
+ ATF_REQUIRE_EQ("status{exitstatus=123}", str.str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(output__signaled_without_core);
+ATF_TEST_CASE_BODY(output__signaled_without_core)
+{
+ const status fake = status::fake_signaled(8, false);
+ std::ostringstream str;
+ str << fake;
+ ATF_REQUIRE_EQ("status{termsig=8, coredump=false}", str.str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(output__signaled_with_core);
+ATF_TEST_CASE_BODY(output__signaled_with_core)
+{
+ const status fake = status::fake_signaled(9, true);
+ std::ostringstream str;
+ str << fake;
+ ATF_REQUIRE_EQ("status{termsig=9, coredump=true}", str.str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__exited);
+ATF_TEST_CASE_BODY(integration__exited)
+{
+ const status exit_success = fork_and_wait(child_exit< EXIT_SUCCESS >);
+ ATF_REQUIRE(exit_success.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, exit_success.exitstatus());
+ ATF_REQUIRE(!exit_success.signaled());
+
+ const status exit_failure = fork_and_wait(child_exit< EXIT_FAILURE >);
+ ATF_REQUIRE(exit_failure.exited());
+ ATF_REQUIRE_EQ(EXIT_FAILURE, exit_failure.exitstatus());
+ ATF_REQUIRE(!exit_failure.signaled());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__signaled);
+ATF_TEST_CASE_BODY(integration__signaled)
+{
+ const status sigterm = fork_and_wait(child_signal< SIGTERM >);
+ ATF_REQUIRE(!sigterm.exited());
+ ATF_REQUIRE(sigterm.signaled());
+ ATF_REQUIRE_EQ(SIGTERM, sigterm.termsig());
+ ATF_REQUIRE(!sigterm.coredump());
+
+ const status sigkill = fork_and_wait(child_signal< SIGKILL >);
+ ATF_REQUIRE(!sigkill.exited());
+ ATF_REQUIRE(sigkill.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, sigkill.termsig());
+ ATF_REQUIRE(!sigkill.coredump());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__coredump);
+ATF_TEST_CASE_BODY(integration__coredump)
+{
+ utils::prepare_coredump_test(this);
+
+ const status coredump = fork_and_wait(child_signal< SIGQUIT >);
+ ATF_REQUIRE(!coredump.exited());
+ ATF_REQUIRE(coredump.signaled());
+ ATF_REQUIRE_EQ(SIGQUIT, coredump.termsig());
+#if !defined(WCOREDUMP)
+ expect_fail("Platform does not support checking for coredump");
+#endif
+ ATF_REQUIRE(coredump.coredump());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, fake_exited);
+ ATF_ADD_TEST_CASE(tcs, fake_signaled);
+
+ ATF_ADD_TEST_CASE(tcs, output__exitstatus);
+ ATF_ADD_TEST_CASE(tcs, output__signaled_without_core);
+ ATF_ADD_TEST_CASE(tcs, output__signaled_with_core);
+
+ ATF_ADD_TEST_CASE(tcs, integration__exited);
+ ATF_ADD_TEST_CASE(tcs, integration__signaled);
+ ATF_ADD_TEST_CASE(tcs, integration__coredump);
+}
diff --git a/utils/process/system.cpp b/utils/process/system.cpp
new file mode 100644
index 000000000000..ac41ddb7daa7
--- /dev/null
+++ b/utils/process/system.cpp
@@ -0,0 +1,59 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/system.hpp"
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+namespace detail = utils::process::detail;
+
+
+/// Indirection to execute the dup2(2) system call.
+int (*detail::syscall_dup2)(const int, const int) = ::dup2;
+
+
+/// Indirection to execute the fork(2) system call.
+pid_t (*detail::syscall_fork)(void) = ::fork;
+
+
+/// Indirection to execute the open(2) system call.
+int (*detail::syscall_open)(const char*, const int, ...) = ::open;
+
+
+/// Indirection to execute the pipe(2) system call.
+int (*detail::syscall_pipe)(int[2]) = ::pipe;
+
+
+/// Indirection to execute the waitpid(2) system call.
+pid_t (*detail::syscall_waitpid)(const pid_t, int*, const int) = ::waitpid;
diff --git a/utils/process/system.hpp b/utils/process/system.hpp
new file mode 100644
index 000000000000..a794876f3579
--- /dev/null
+++ b/utils/process/system.hpp
@@ -0,0 +1,66 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/system.hpp
+/// Indirection to perform system calls.
+///
+/// The indirections exposed in this file are provided to allow unit-testing of
+/// particular system behaviors (e.g. failures). The caller of a routine in
+/// this library is allowed, for testing purposes only, to explicitly replace
+/// the pointers in this file with custom functions to inject a particular
+/// behavior into the library code.
+///
+/// Do not include this header from other header files.
+///
+/// It may be nice to go one step further and completely abstract the library
+/// functions in here to provide exception-based error reporting.
+
+#if !defined(UTILS_PROCESS_SYSTEM_HPP)
+#define UTILS_PROCESS_SYSTEM_HPP
+
+extern "C" {
+#include <unistd.h>
+}
+
+namespace utils {
+namespace process {
+namespace detail {
+
+
+extern int (*syscall_dup2)(const int, const int);
+extern pid_t (*syscall_fork)(void);
+extern int (*syscall_open)(const char*, const int, ...);
+extern int (*syscall_pipe)(int[2]);
+extern pid_t (*syscall_waitpid)(const pid_t, int*, const int);
+
+
+} // namespace detail
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_SYSTEM_HPP)
diff --git a/utils/process/systembuf.cpp b/utils/process/systembuf.cpp
new file mode 100644
index 000000000000..661b336221ac
--- /dev/null
+++ b/utils/process/systembuf.cpp
@@ -0,0 +1,152 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/systembuf.hpp"
+
+extern "C" {
+#include <unistd.h>
+}
+
+#include "utils/auto_array.ipp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+
+using utils::process::systembuf;
+
+
+/// Private implementation fields for systembuf.
+struct systembuf::impl : utils::noncopyable {
+ /// File descriptor attached to the systembuf.
+ int _fd;
+
+ /// Size of the _read_buf and _write_buf buffers.
+ std::size_t _bufsize;
+
+ /// In-memory buffer for read operations.
+ utils::auto_array< char > _read_buf;
+
+ /// In-memory buffer for write operations.
+ utils::auto_array< char > _write_buf;
+
+ /// Initializes private implementation data.
+ ///
+ /// \param fd The file descriptor.
+ /// \param bufsize The size of the created read and write buffers.
+ impl(const int fd, const std::size_t bufsize) :
+ _fd(fd),
+ _bufsize(bufsize),
+ _read_buf(new char[bufsize]),
+ _write_buf(new char[bufsize])
+ {
+ }
+};
+
+
+/// Constructs a new systembuf based on an open file descriptor.
+///
+/// This grabs ownership of the file descriptor.
+///
+/// \param fd The file descriptor to wrap. Must be open and valid.
+/// \param bufsize The size to use for the internal read/write buffers.
+systembuf::systembuf(const int fd, std::size_t bufsize) :
+ _pimpl(new impl(fd, bufsize))
+{
+ setp(_pimpl->_write_buf.get(), _pimpl->_write_buf.get() + _pimpl->_bufsize);
+}
+
+
+/// Destroys a systembuf object.
+///
+/// \post The file descriptor attached to this systembuf is closed.
+systembuf::~systembuf(void)
+{
+ ::close(_pimpl->_fd);
+}
+
+
+/// Reads new data when the systembuf read buffer underflows.
+///
+/// \return The new character to be read, or EOF if no more.
+systembuf::int_type
+systembuf::underflow(void)
+{
+ PRE(gptr() >= egptr());
+
+ bool ok;
+ ssize_t cnt = ::read(_pimpl->_fd, _pimpl->_read_buf.get(),
+ _pimpl->_bufsize);
+ ok = (cnt != -1 && cnt != 0);
+
+ if (!ok)
+ return traits_type::eof();
+ else {
+ setg(_pimpl->_read_buf.get(), _pimpl->_read_buf.get(),
+ _pimpl->_read_buf.get() + cnt);
+ return traits_type::to_int_type(*gptr());
+ }
+}
+
+
+/// Writes data to the file descriptor when the write buffer overflows.
+///
+/// \param c The character that causes the overflow.
+///
+/// \return EOF if error, some other value for success.
+///
+/// \throw something TODO(jmmv): According to the documentation, it is OK for
+/// this method to throw in case of errors. Revisit this code to see if we
+/// can do better.
+systembuf::int_type
+systembuf::overflow(int c)
+{
+ PRE(pptr() >= epptr());
+ if (sync() == -1)
+ return traits_type::eof();
+ if (!traits_type::eq_int_type(c, traits_type::eof())) {
+ traits_type::assign(*pptr(), c);
+ pbump(1);
+ }
+ return traits_type::not_eof(c);
+}
+
+
+/// Synchronizes the stream with the file descriptor.
+///
+/// \return 0 on success, -1 on error.
+int
+systembuf::sync(void)
+{
+ ssize_t cnt = pptr() - pbase();
+
+ bool ok;
+ ok = ::write(_pimpl->_fd, pbase(), cnt) == cnt;
+
+ if (ok)
+ pbump(-cnt);
+ return ok ? 0 : -1;
+}
diff --git a/utils/process/systembuf.hpp b/utils/process/systembuf.hpp
new file mode 100644
index 000000000000..c89c9108dc4b
--- /dev/null
+++ b/utils/process/systembuf.hpp
@@ -0,0 +1,71 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/systembuf.hpp
+/// Provides the utils::process::systembuf class.
+
+#if !defined(UTILS_PROCESS_SYSTEMBUF_HPP)
+#define UTILS_PROCESS_SYSTEMBUF_HPP
+
+#include "utils/process/systembuf_fwd.hpp"
+
+#include <cstddef>
+#include <memory>
+#include <streambuf>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace process {
+
+
+/// A std::streambuf implementation for raw file descriptors.
+///
+/// This class grabs ownership of the file descriptor. I.e. when the class is
+/// destroyed, the file descriptor is closed unconditionally.
+class systembuf : public std::streambuf, noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+protected:
+ int_type underflow(void);
+ int_type overflow(int);
+ int sync(void);
+
+public:
+ explicit systembuf(const int, std::size_t = 8192);
+ ~systembuf(void);
+};
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_SYSTEMBUF_HPP)
diff --git a/utils/process/systembuf_fwd.hpp b/utils/process/systembuf_fwd.hpp
new file mode 100644
index 000000000000..b3e341336b1d
--- /dev/null
+++ b/utils/process/systembuf_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/process/systembuf_fwd.hpp
+/// Forward declarations for utils/process/systembuf.hpp
+
+#if !defined(UTILS_PROCESS_SYSTEMBUF_FWD_HPP)
+#define UTILS_PROCESS_SYSTEMBUF_FWD_HPP
+
+namespace utils {
+namespace process {
+
+
+class systembuf;
+
+
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_SYSTEMBUF_FWD_HPP)
diff --git a/utils/process/systembuf_test.cpp b/utils/process/systembuf_test.cpp
new file mode 100644
index 000000000000..ef9ff1930cf6
--- /dev/null
+++ b/utils/process/systembuf_test.cpp
@@ -0,0 +1,166 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/process/systembuf.hpp"
+
+extern "C" {
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+#include <fstream>
+
+#include <atf-c++.hpp>
+
+using utils::process::systembuf;
+
+
+static void
+check_data(std::istream& is, std::size_t length)
+{
+ char ch = 'A', chr;
+ std::size_t cnt = 0;
+ while (is >> chr) {
+ ATF_REQUIRE_EQ(ch, chr);
+ if (ch == 'Z')
+ ch = 'A';
+ else
+ ch++;
+ cnt++;
+ }
+ ATF_REQUIRE_EQ(cnt, length);
+}
+
+
+static void
+write_data(std::ostream& os, std::size_t length)
+{
+ char ch = 'A';
+ for (std::size_t i = 0; i < length; i++) {
+ os << ch;
+ if (ch == 'Z')
+ ch = 'A';
+ else
+ ch++;
+ }
+ os.flush();
+}
+
+
+static void
+test_read(std::size_t length, std::size_t bufsize)
+{
+ std::ofstream f("test_read.txt");
+ write_data(f, length);
+ f.close();
+
+ int fd = ::open("test_read.txt", O_RDONLY);
+ ATF_REQUIRE(fd != -1);
+ systembuf sb(fd, bufsize);
+ std::istream is(&sb);
+ check_data(is, length);
+ ::close(fd);
+ ::unlink("test_read.txt");
+}
+
+
+static void
+test_write(std::size_t length, std::size_t bufsize)
+{
+ int fd = ::open("test_write.txt", O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ ATF_REQUIRE(fd != -1);
+ systembuf sb(fd, bufsize);
+ std::ostream os(&sb);
+ write_data(os, length);
+ ::close(fd);
+
+ std::ifstream is("test_write.txt");
+ check_data(is, length);
+ is.close();
+ ::unlink("test_write.txt");
+}
+
+
+ATF_TEST_CASE(short_read);
+ATF_TEST_CASE_HEAD(short_read)
+{
+ set_md_var("descr", "Tests that a short read (one that fits in the "
+ "internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(short_read)
+{
+ test_read(64, 1024);
+}
+
+
+ATF_TEST_CASE(long_read);
+ATF_TEST_CASE_HEAD(long_read)
+{
+ set_md_var("descr", "Tests that a long read (one that does not fit in "
+ "the internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(long_read)
+{
+ test_read(64 * 1024, 1024);
+}
+
+
+ATF_TEST_CASE(short_write);
+ATF_TEST_CASE_HEAD(short_write)
+{
+ set_md_var("descr", "Tests that a short write (one that fits in the "
+ "internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(short_write)
+{
+ test_write(64, 1024);
+}
+
+
+ATF_TEST_CASE(long_write);
+ATF_TEST_CASE_HEAD(long_write)
+{
+ set_md_var("descr", "Tests that a long write (one that does not fit "
+ "in the internal buffer) works when using systembuf");
+}
+ATF_TEST_CASE_BODY(long_write)
+{
+ test_write(64 * 1024, 1024);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, short_read);
+ ATF_ADD_TEST_CASE(tcs, long_read);
+ ATF_ADD_TEST_CASE(tcs, short_write);
+ ATF_ADD_TEST_CASE(tcs, long_write);
+}
diff --git a/utils/sanity.cpp b/utils/sanity.cpp
new file mode 100644
index 000000000000..7978167d83ff
--- /dev/null
+++ b/utils/sanity.cpp
@@ -0,0 +1,194 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sanity.hpp"
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+
+
+namespace {
+
+
+/// List of fatal signals to be intercepted by the sanity code.
+///
+/// The tests hardcode this list; update them whenever the list gets updated.
+static int fatal_signals[] = { SIGABRT, SIGBUS, SIGSEGV, 0 };
+
+
+/// The path to the log file to report on crashes. Be aware that this is empty
+/// until install_crash_handlers() is called.
+static std::string logfile;
+
+
+/// Prints a message to stderr.
+///
+/// Note that this runs from a signal handler. Calling write() is OK.
+///
+/// \param message The message to print.
+static void
+err_write(const std::string& message)
+{
+ if (::write(STDERR_FILENO, message.c_str(), message.length()) == -1) {
+ // We are crashing. If ::write fails, there is not much we could do,
+ // specially considering that we are running within a signal handler.
+ // Just ignore the error.
+ }
+}
+
+
+/// The crash handler for fatal signals.
+///
+/// The sole purpose of this is to print some informational data before
+/// reraising the original signal.
+///
+/// \param signo The received signal.
+static void
+crash_handler(const int signo)
+{
+ PRE(!logfile.empty());
+
+ err_write(F("*** Fatal signal %s received\n") % signo);
+ err_write(F("*** Log file is %s\n") % logfile);
+ err_write(F("*** Please report this problem to %s detailing what you were "
+ "doing before the crash happened; if possible, include the log "
+ "file mentioned above\n") % PACKAGE_BUGREPORT);
+
+ /// The handler is installed with SA_RESETHAND, so this is safe to do. We
+ /// really want to call the default handler to generate any possible core
+ /// dumps.
+ ::kill(::getpid(), signo);
+}
+
+
+/// Installs a handler for a fatal signal representing a crash.
+///
+/// When the specified signal is captured, the crash_handler() will be called to
+/// print some informational details to the user and, later, the signal will be
+/// redelivered using the default handler to obtain a core dump.
+///
+/// \param signo The fatal signal for which to install a handler.
+static void
+install_one_crash_handler(const int signo)
+{
+ struct ::sigaction sa;
+ sa.sa_handler = crash_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESETHAND;
+
+ if (::sigaction(signo, &sa, NULL) == -1) {
+ const int original_errno = errno;
+ LW(F("Could not install crash handler for signal %s: %s") %
+ signo % std::strerror(original_errno));
+ } else
+ LD(F("Installed crash handler for signal %s") % signo);
+}
+
+
+/// Returns a textual representation of an assertion type.
+///
+/// The textual representation is user facing.
+///
+/// \param type The type of the assertion. If the type is unknown for whatever
+/// reason, a special message is returned. The code cannot abort in such a
+/// case because this code is dealing for assertion errors.
+///
+/// \return A textual description of the assertion type.
+static std::string
+format_type(const utils::assert_type type)
+{
+ switch (type) {
+ case utils::invariant: return "Invariant check failed";
+ case utils::postcondition: return "Postcondition check failed";
+ case utils::precondition: return "Precondition check failed";
+ case utils::unreachable: return "Unreachable point reached";
+ default: return "UNKNOWN ASSERTION TYPE";
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Raises an assertion error.
+///
+/// This function prints information about the assertion failure and terminates
+/// execution immediately by calling std::abort(). This ensures a coredump so
+/// that the failure can be analyzed later.
+///
+/// \param type The assertion type; this influences the printed message.
+/// \param file The file in which the assertion failed.
+/// \param line The line in which the assertion failed.
+/// \param message The failure message associated to the condition.
+void
+utils::sanity_failure(const assert_type type, const char* file,
+ const size_t line, const std::string& message)
+{
+ std::cerr << "*** " << file << ":" << line << ": " << format_type(type);
+ if (!message.empty())
+ std::cerr << ": " << message << "\n";
+ else
+ std::cerr << "\n";
+ std::abort();
+}
+
+
+/// Installs persistent handlers for crash signals.
+///
+/// Should be called at the very beginning of the execution of the program to
+/// ensure that a signal handler for fatal crash signals is installed.
+///
+/// \pre The function has not been called before.
+///
+/// \param logfile_ The path to the log file to report during a crash.
+void
+utils::install_crash_handlers(const std::string& logfile_)
+{
+ static bool installed = false;
+ PRE(!installed);
+ logfile = logfile_;
+
+ for (const int* iter = &fatal_signals[0]; *iter != 0; iter++)
+ install_one_crash_handler(*iter);
+
+ installed = true;
+}
diff --git a/utils/sanity.hpp b/utils/sanity.hpp
new file mode 100644
index 000000000000..6b126f984999
--- /dev/null
+++ b/utils/sanity.hpp
@@ -0,0 +1,183 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sanity.hpp
+///
+/// Set of macros that replace the standard assert macro with more semantical
+/// expressivity and meaningful diagnostics. Code should never use assert
+/// directly.
+///
+/// In general, the checks performed by the macros in this code are only
+/// executed if the code is built with debugging support (that is, if the NDEBUG
+/// macro is NOT defined).
+
+#if !defined(UTILS_SANITY_HPP)
+#define UTILS_SANITY_HPP
+
+#include "utils/sanity_fwd.hpp"
+
+#include <cstddef>
+#include <string>
+
+#include "utils/defs.hpp"
+
+namespace utils {
+
+
+void sanity_failure(const assert_type, const char*, const size_t,
+ const std::string&) UTILS_NORETURN;
+
+
+void install_crash_handlers(const std::string&);
+
+
+} // namespace utils
+
+
+/// \def _UTILS_ASSERT(type, expr, message)
+/// \brief Performs an assertion check.
+///
+/// This macro is internal and should not be used directly.
+///
+/// Ensures that the given expression expr is true and, if not, terminates
+/// execution by calling utils::sanity_failure(). The check is only performed
+/// in debug builds.
+///
+/// \param type The assertion type as defined by assert_type.
+/// \param expr A boolean expression.
+/// \param message A string describing the nature of the error.
+#if !defined(NDEBUG)
+# define _UTILS_ASSERT(type, expr, message) \
+ do { \
+ if (!(expr)) \
+ utils::sanity_failure(type, __FILE__, __LINE__, message); \
+ } while (0)
+#else // defined(NDEBUG)
+# define _UTILS_ASSERT(type, expr, message) do {} while (0)
+#endif // !defined(NDEBUG)
+
+
+/// Ensures that an invariant holds.
+///
+/// If the invariant does not hold, execution is immediately terminated. The
+/// check is only performed in debug builds.
+///
+/// The error message printed by this macro is a textual representation of the
+/// boolean condition. If you want to provide a custom error message, use
+/// INV_MSG instead.
+///
+/// \param expr A boolean expression describing the invariant.
+#define INV(expr) _UTILS_ASSERT(utils::invariant, expr, #expr)
+
+
+/// Ensures that an invariant holds using a custom error message.
+///
+/// If the invariant does not hold, execution is immediately terminated. The
+/// check is only performed in debug builds.
+///
+/// \param expr A boolean expression describing the invariant.
+/// \param msg The error message to print if the condition is false.
+#define INV_MSG(expr, msg) _UTILS_ASSERT(utils::invariant, expr, msg)
+
+
+/// Ensures that a precondition holds.
+///
+/// If the precondition does not hold, execution is immediately terminated. The
+/// check is only performed in debug builds.
+///
+/// The error message printed by this macro is a textual representation of the
+/// boolean condition. If you want to provide a custom error message, use
+/// PRE_MSG instead.
+///
+/// \param expr A boolean expression describing the precondition.
+#define PRE(expr) _UTILS_ASSERT(utils::precondition, expr, #expr)
+
+
+/// Ensures that a precondition holds using a custom error message.
+///
+/// If the precondition does not hold, execution is immediately terminated. The
+/// check is only performed in debug builds.
+///
+/// \param expr A boolean expression describing the precondition.
+/// \param msg The error message to print if the condition is false.
+#define PRE_MSG(expr, msg) _UTILS_ASSERT(utils::precondition, expr, msg)
+
+
+/// Ensures that an postcondition holds.
+///
+/// If the postcondition does not hold, execution is immediately terminated.
+/// The check is only performed in debug builds.
+///
+/// The error message printed by this macro is a textual representation of the
+/// boolean condition. If you want to provide a custom error message, use
+/// POST_MSG instead.
+///
+/// \param expr A boolean expression describing the postcondition.
+#define POST(expr) _UTILS_ASSERT(utils::postcondition, expr, #expr)
+
+
+/// Ensures that a postcondition holds using a custom error message.
+///
+/// If the postcondition does not hold, execution is immediately terminated.
+/// The check is only performed in debug builds.
+///
+/// \param expr A boolean expression describing the postcondition.
+/// \param msg The error message to print if the condition is false.
+#define POST_MSG(expr, msg) _UTILS_ASSERT(utils::postcondition, expr, msg)
+
+
+/// Ensures that a code path is not reached.
+///
+/// If the code path in which this macro is located is reached, execution is
+/// immediately terminated. Given that such a condition is critical for the
+/// execution of the program (and to prevent build failures due to some code
+/// paths not initializing variables, for example), this condition is fatal both
+/// in debug and production builds.
+///
+/// The error message printed by this macro is a textual representation of the
+/// boolean condition. If you want to provide a custom error message, use
+/// POST_MSG instead.
+#define UNREACHABLE UNREACHABLE_MSG("")
+
+
+/// Ensures that a code path is not reached using a custom error message.
+///
+/// If the code path in which this macro is located is reached, execution is
+/// immediately terminated. Given that such a condition is critical for the
+/// execution of the program (and to prevent build failures due to some code
+/// paths not initializing variables, for example), this condition is fatal both
+/// in debug and production builds.
+///
+/// \param msg The error message to print if the condition is false.
+#define UNREACHABLE_MSG(msg) \
+ do { \
+ utils::sanity_failure(utils::unreachable, __FILE__, __LINE__, msg); \
+ } while (0)
+
+
+#endif // !defined(UTILS_SANITY_HPP)
diff --git a/utils/sanity_fwd.hpp b/utils/sanity_fwd.hpp
new file mode 100644
index 000000000000..98a897c0ff39
--- /dev/null
+++ b/utils/sanity_fwd.hpp
@@ -0,0 +1,52 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sanity_fwd.hpp
+/// Forward declarations for utils/sanity.hpp
+
+#if !defined(UTILS_SANITY_FWD_HPP)
+#define UTILS_SANITY_FWD_HPP
+
+namespace utils {
+
+
+/// Enumeration to define the assertion type.
+///
+/// The assertion type is used by the module to format the assertion messages
+/// appropriately.
+enum assert_type {
+ invariant,
+ postcondition,
+ precondition,
+ unreachable,
+};
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_SANITY_FWD_HPP)
diff --git a/utils/sanity_test.cpp b/utils/sanity_test.cpp
new file mode 100644
index 000000000000..54844fb75d64
--- /dev/null
+++ b/utils/sanity_test.cpp
@@ -0,0 +1,322 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sanity.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/test_utils.ipp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+
+
+#define FILE_REGEXP __FILE__ ":[0-9]+: "
+
+
+static const fs::path Stdout_File("stdout.txt");
+static const fs::path Stderr_File("stderr.txt");
+
+
+#if NDEBUG
+static bool NDebug = true;
+#else
+static bool NDebug = false;
+#endif
+
+
+template< typename Function >
+static process::status
+run_test(Function function)
+{
+ utils::avoid_coredump_on_crash();
+
+ const process::status status = process::child::fork_files(
+ function, Stdout_File, Stderr_File)->wait();
+ atf::utils::cat_file(Stdout_File.str(), "Helper stdout: ");
+ atf::utils::cat_file(Stderr_File.str(), "Helper stderr: ");
+ return status;
+}
+
+
+static void
+verify_success(const process::status& status)
+{
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+ ATF_REQUIRE(atf::utils::grep_file("Before test", Stdout_File.str()));
+ ATF_REQUIRE(atf::utils::grep_file("After test", Stdout_File.str()));
+}
+
+
+static void
+verify_failed(const process::status& status, const char* type,
+ const char* exp_message, const bool check_ndebug)
+{
+ if (check_ndebug && NDebug) {
+ std::cout << "Built with NDEBUG; skipping verification\n";
+ verify_success(status);
+ } else {
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGABRT, status.termsig());
+ ATF_REQUIRE(atf::utils::grep_file("Before test", Stdout_File.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("After test", Stdout_File.str()));
+ if (exp_message != NULL)
+ ATF_REQUIRE(atf::utils::grep_file(F(FILE_REGEXP "%s: %s") %
+ type % exp_message,
+ Stderr_File.str()));
+ else
+ ATF_REQUIRE(atf::utils::grep_file(F(FILE_REGEXP "%s") % type,
+ Stderr_File.str()));
+ }
+}
+
+
+template< bool Expression, bool WithMessage >
+static void
+do_inv_test(void)
+{
+ std::cout << "Before test\n";
+ if (WithMessage)
+ INV_MSG(Expression, "Custom message");
+ else
+ INV(Expression);
+ std::cout << "After test\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(inv__holds);
+ATF_TEST_CASE_BODY(inv__holds)
+{
+ const process::status status = run_test(do_inv_test< true, false >);
+ verify_success(status);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(inv__triggers_default_message);
+ATF_TEST_CASE_BODY(inv__triggers_default_message)
+{
+ const process::status status = run_test(do_inv_test< false, false >);
+ verify_failed(status, "Invariant check failed", "Expression", true);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(inv__triggers_custom_message);
+ATF_TEST_CASE_BODY(inv__triggers_custom_message)
+{
+ const process::status status = run_test(do_inv_test< false, true >);
+ verify_failed(status, "Invariant check failed", "Custom", true);
+}
+
+
+template< bool Expression, bool WithMessage >
+static void
+do_pre_test(void)
+{
+ std::cout << "Before test\n";
+ if (WithMessage)
+ PRE_MSG(Expression, "Custom message");
+ else
+ PRE(Expression);
+ std::cout << "After test\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(pre__holds);
+ATF_TEST_CASE_BODY(pre__holds)
+{
+ const process::status status = run_test(do_pre_test< true, false >);
+ verify_success(status);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(pre__triggers_default_message);
+ATF_TEST_CASE_BODY(pre__triggers_default_message)
+{
+ const process::status status = run_test(do_pre_test< false, false >);
+ verify_failed(status, "Precondition check failed", "Expression", true);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(pre__triggers_custom_message);
+ATF_TEST_CASE_BODY(pre__triggers_custom_message)
+{
+ const process::status status = run_test(do_pre_test< false, true >);
+ verify_failed(status, "Precondition check failed", "Custom", true);
+}
+
+
+template< bool Expression, bool WithMessage >
+static void
+do_post_test(void)
+{
+ std::cout << "Before test\n";
+ if (WithMessage)
+ POST_MSG(Expression, "Custom message");
+ else
+ POST(Expression);
+ std::cout << "After test\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(post__holds);
+ATF_TEST_CASE_BODY(post__holds)
+{
+ const process::status status = run_test(do_post_test< true, false >);
+ verify_success(status);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(post__triggers_default_message);
+ATF_TEST_CASE_BODY(post__triggers_default_message)
+{
+ const process::status status = run_test(do_post_test< false, false >);
+ verify_failed(status, "Postcondition check failed", "Expression", true);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(post__triggers_custom_message);
+ATF_TEST_CASE_BODY(post__triggers_custom_message)
+{
+ const process::status status = run_test(do_post_test< false, true >);
+ verify_failed(status, "Postcondition check failed", "Custom", true);
+}
+
+
+template< bool WithMessage >
+static void
+do_unreachable_test(void)
+{
+ std::cout << "Before test\n";
+ if (WithMessage)
+ UNREACHABLE_MSG("Custom message");
+ else
+ UNREACHABLE;
+ std::cout << "After test\n";
+ std::exit(EXIT_SUCCESS);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unreachable__default_message);
+ATF_TEST_CASE_BODY(unreachable__default_message)
+{
+ const process::status status = run_test(do_unreachable_test< false >);
+ verify_failed(status, "Unreachable point reached", NULL, false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unreachable__custom_message);
+ATF_TEST_CASE_BODY(unreachable__custom_message)
+{
+ const process::status status = run_test(do_unreachable_test< true >);
+ verify_failed(status, "Unreachable point reached", "Custom", false);
+}
+
+
+template< int Signo >
+static void
+do_crash_handler_test(void)
+{
+ utils::install_crash_handlers("test-log.txt");
+ ::kill(::getpid(), Signo);
+ std::cout << "After signal\n";
+ std::exit(EXIT_FAILURE);
+}
+
+
+template< int Signo >
+static void
+crash_handler_test(void)
+{
+ utils::avoid_coredump_on_crash();
+
+ const process::status status = run_test(do_crash_handler_test< Signo >);
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(Signo, status.termsig());
+ ATF_REQUIRE(atf::utils::grep_file(F("Fatal signal %s") % Signo,
+ Stderr_File.str()));
+ ATF_REQUIRE(atf::utils::grep_file("Log file is test-log.txt",
+ Stderr_File.str()));
+ ATF_REQUIRE(!atf::utils::grep_file("After signal", Stdout_File.str()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(install_crash_handlers__sigabrt);
+ATF_TEST_CASE_BODY(install_crash_handlers__sigabrt)
+{
+ crash_handler_test< SIGABRT >();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(install_crash_handlers__sigbus);
+ATF_TEST_CASE_BODY(install_crash_handlers__sigbus)
+{
+ crash_handler_test< SIGBUS >();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(install_crash_handlers__sigsegv);
+ATF_TEST_CASE_BODY(install_crash_handlers__sigsegv)
+{
+ crash_handler_test< SIGSEGV >();
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, inv__holds);
+ ATF_ADD_TEST_CASE(tcs, inv__triggers_default_message);
+ ATF_ADD_TEST_CASE(tcs, inv__triggers_custom_message);
+ ATF_ADD_TEST_CASE(tcs, pre__holds);
+ ATF_ADD_TEST_CASE(tcs, pre__triggers_default_message);
+ ATF_ADD_TEST_CASE(tcs, pre__triggers_custom_message);
+ ATF_ADD_TEST_CASE(tcs, post__holds);
+ ATF_ADD_TEST_CASE(tcs, post__triggers_default_message);
+ ATF_ADD_TEST_CASE(tcs, post__triggers_custom_message);
+ ATF_ADD_TEST_CASE(tcs, unreachable__default_message);
+ ATF_ADD_TEST_CASE(tcs, unreachable__custom_message);
+
+ ATF_ADD_TEST_CASE(tcs, install_crash_handlers__sigabrt);
+ ATF_ADD_TEST_CASE(tcs, install_crash_handlers__sigbus);
+ ATF_ADD_TEST_CASE(tcs, install_crash_handlers__sigsegv);
+}
diff --git a/utils/signals/Kyuafile b/utils/signals/Kyuafile
new file mode 100644
index 000000000000..09da3e166cd2
--- /dev/null
+++ b/utils/signals/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="interrupts_test"}
+atf_test_program{name="misc_test"}
+atf_test_program{name="programmer_test"}
+atf_test_program{name="timer_test"}
diff --git a/utils/signals/Makefile.am.inc b/utils/signals/Makefile.am.inc
new file mode 100644
index 000000000000..b01089c80fea
--- /dev/null
+++ b/utils/signals/Makefile.am.inc
@@ -0,0 +1,73 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/signals/exceptions.cpp
+libutils_a_SOURCES += utils/signals/exceptions.hpp
+libutils_a_SOURCES += utils/signals/interrupts.cpp
+libutils_a_SOURCES += utils/signals/interrupts.hpp
+libutils_a_SOURCES += utils/signals/interrupts_fwd.hpp
+libutils_a_SOURCES += utils/signals/misc.cpp
+libutils_a_SOURCES += utils/signals/misc.hpp
+libutils_a_SOURCES += utils/signals/programmer.cpp
+libutils_a_SOURCES += utils/signals/programmer.hpp
+libutils_a_SOURCES += utils/signals/programmer_fwd.hpp
+libutils_a_SOURCES += utils/signals/timer.cpp
+libutils_a_SOURCES += utils/signals/timer.hpp
+libutils_a_SOURCES += utils/signals/timer_fwd.hpp
+
+if WITH_ATF
+tests_utils_signalsdir = $(pkgtestsdir)/utils/signals
+
+tests_utils_signals_DATA = utils/signals/Kyuafile
+EXTRA_DIST += $(tests_utils_signals_DATA)
+
+tests_utils_signals_PROGRAMS = utils/signals/exceptions_test
+utils_signals_exceptions_test_SOURCES = utils/signals/exceptions_test.cpp
+utils_signals_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/interrupts_test
+utils_signals_interrupts_test_SOURCES = utils/signals/interrupts_test.cpp
+utils_signals_interrupts_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_interrupts_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/misc_test
+utils_signals_misc_test_SOURCES = utils/signals/misc_test.cpp
+utils_signals_misc_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_misc_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/programmer_test
+utils_signals_programmer_test_SOURCES = utils/signals/programmer_test.cpp
+utils_signals_programmer_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_programmer_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/timer_test
+utils_signals_timer_test_SOURCES = utils/signals/timer_test.cpp
+utils_signals_timer_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_timer_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/signals/exceptions.cpp b/utils/signals/exceptions.cpp
new file mode 100644
index 000000000000..70e0dbe8a5d1
--- /dev/null
+++ b/utils/signals/exceptions.cpp
@@ -0,0 +1,102 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/exceptions.hpp"
+
+#include <cstring>
+
+#include "utils/format/macros.hpp"
+
+namespace signals = utils::signals;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+signals::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+signals::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new interrupted error.
+///
+/// \param signo_ The signal that caused the interrupt.
+signals::interrupted_error::interrupted_error(const int signo_) :
+ error(F("Interrupted by signal %s") % signo_),
+ _signo(signo_)
+{
+}
+
+
+/// Destructor for the error.
+signals::interrupted_error::~interrupted_error(void) throw()
+{
+}
+
+
+/// Queries the signal number of the interruption.
+///
+/// \return A signal number.
+int
+signals::interrupted_error::signo(void) const
+{
+ return _signo;
+}
+
+
+/// Constructs a new error based on an errno code.
+///
+/// \param message_ The message describing what caused the error.
+/// \param errno_ The error code.
+signals::system_error::system_error(const std::string& message_,
+ const int errno_) :
+ error(F("%s: %s") % message_ % strerror(errno_)),
+ _original_errno(errno_)
+{
+}
+
+
+/// Destructor for the error.
+signals::system_error::~system_error(void) throw()
+{
+}
+
+
+/// \return The original errno value.
+int
+signals::system_error::original_errno(void) const throw()
+{
+ return _original_errno;
+}
diff --git a/utils/signals/exceptions.hpp b/utils/signals/exceptions.hpp
new file mode 100644
index 000000000000..35cd2c9e8168
--- /dev/null
+++ b/utils/signals/exceptions.hpp
@@ -0,0 +1,83 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/exceptions.hpp
+/// Exception types raised by the signals module.
+
+#if !defined(UTILS_SIGNALS_EXCEPTIONS_HPP)
+#define UTILS_SIGNALS_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+namespace utils {
+namespace signals {
+
+
+/// Base exceptions for signals errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Denotes the reception of a signal to controlledly terminate execution.
+class interrupted_error : public error {
+ /// Signal that caused the interrupt.
+ int _signo;
+
+public:
+ explicit interrupted_error(const int signo_);
+ ~interrupted_error(void) throw();
+
+ int signo(void) const;
+};
+
+
+/// Exceptions for errno-based errors.
+///
+/// TODO(jmmv): This code is duplicated in, at least, utils::fs. Figure
+/// out a way to reuse this exception while maintaining the correct inheritance
+/// (i.e. be able to keep it as a child of signals::error).
+class system_error : public error {
+ /// Error number describing this libc error condition.
+ int _original_errno;
+
+public:
+ explicit system_error(const std::string&, const int);
+ ~system_error(void) throw();
+
+ int original_errno(void) const throw();
+};
+
+
+} // namespace signals
+} // namespace utils
+
+
+#endif // !defined(UTILS_SIGNALS_EXCEPTIONS_HPP)
diff --git a/utils/signals/exceptions_test.cpp b/utils/signals/exceptions_test.cpp
new file mode 100644
index 000000000000..40db536f1a8c
--- /dev/null
+++ b/utils/signals/exceptions_test.cpp
@@ -0,0 +1,73 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/exceptions.hpp"
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+
+namespace signals = utils::signals;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const signals::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupted_error);
+ATF_TEST_CASE_BODY(interrupted_error)
+{
+ const signals::interrupted_error e(5);
+ ATF_REQUIRE(std::strcmp("Interrupted by signal 5", e.what()) == 0);
+ ATF_REQUIRE_EQ(5, e.signo());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(system_error);
+ATF_TEST_CASE_BODY(system_error)
+{
+ const signals::system_error e("Call failed", ENOENT);
+ const std::string expected = F("Call failed: %s") % std::strerror(ENOENT);
+ ATF_REQUIRE_EQ(expected, e.what());
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, interrupted_error);
+ ATF_ADD_TEST_CASE(tcs, system_error);
+}
diff --git a/utils/signals/interrupts.cpp b/utils/signals/interrupts.cpp
new file mode 100644
index 000000000000..956a83c66802
--- /dev/null
+++ b/utils/signals/interrupts.cpp
@@ -0,0 +1,309 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/interrupts.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <set>
+
+#include "utils/logging/macros.hpp"
+#include "utils/process/operations.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace signals = utils::signals;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// The interrupt signal that fired, or -1 if none.
+static volatile int fired_signal = -1;
+
+
+/// Collection of PIDs.
+typedef std::set< pid_t > pids_set;
+
+
+/// List of processes to kill upon reception of a signal.
+static pids_set pids_to_kill;
+
+
+/// Programmer status for the SIGHUP signal.
+static std::auto_ptr< signals::programmer > sighup_handler;
+/// Programmer status for the SIGINT signal.
+static std::auto_ptr< signals::programmer > sigint_handler;
+/// Programmer status for the SIGTERM signal.
+static std::auto_ptr< signals::programmer > sigterm_handler;
+
+
+/// Signal mask to restore after exiting a signal inhibited section.
+static sigset_t global_old_sigmask;
+
+
+/// Whether there is an interrupts_handler object in existence or not.
+static bool interrupts_handler_active = false;
+
+
+/// Whether there is an interrupts_inhibiter object in existence or not.
+static std::size_t interrupts_inhibiter_active = 0;
+
+
+/// Generic handler to capture interrupt signals.
+///
+/// From this handler, we record that an interrupt has happened so that
+/// check_interrupt() can know whether there execution has to be stopped or not.
+/// We also terminate any of our child processes (started by the
+/// utils::process::children class) so that any ongoing wait(2) system calls
+/// terminate.
+///
+/// \param signo The signal that caused this handler to be called.
+static void
+signal_handler(const int signo)
+{
+ static const char* message = "[-- Signal caught; please wait for "
+ "cleanup --]\n";
+ if (::write(STDERR_FILENO, message, std::strlen(message)) == -1) {
+ // We are exiting: the message printed here is only for informational
+ // purposes. If we fail to print it (which probably means something
+ // is really bad), there is not much we can do within the signal
+ // handler, so just ignore this.
+ }
+
+ fired_signal = signo;
+
+ for (pids_set::const_iterator iter = pids_to_kill.begin();
+ iter != pids_to_kill.end(); ++iter) {
+ process::terminate_group(*iter);
+ }
+}
+
+
+/// Installs signal handlers for potential interrupts.
+///
+/// \pre Must not have been called before.
+/// \post The various sig*_handler global variables are atomically updated.
+static void
+setup_handlers(void)
+{
+ PRE(sighup_handler.get() == NULL);
+ PRE(sigint_handler.get() == NULL);
+ PRE(sigterm_handler.get() == NULL);
+
+ // Create the handlers on the stack first so that, if any of them fails, the
+ // stack unwinding cleans things up.
+ std::auto_ptr< signals::programmer > tmp_sighup_handler(
+ new signals::programmer(SIGHUP, signal_handler));
+ std::auto_ptr< signals::programmer > tmp_sigint_handler(
+ new signals::programmer(SIGINT, signal_handler));
+ std::auto_ptr< signals::programmer > tmp_sigterm_handler(
+ new signals::programmer(SIGTERM, signal_handler));
+
+ // Now, update the global pointers, which is an operation that cannot fail.
+ sighup_handler = tmp_sighup_handler;
+ sigint_handler = tmp_sigint_handler;
+ sigterm_handler = tmp_sigterm_handler;
+}
+
+
+/// Uninstalls the signal handlers installed by setup_handlers().
+static void
+cleanup_handlers(void)
+{
+ sighup_handler->unprogram(); sighup_handler.reset(NULL);
+ sigint_handler->unprogram(); sigint_handler.reset(NULL);
+ sigterm_handler->unprogram(); sigterm_handler.reset(NULL);
+}
+
+
+
+/// Masks the signals installed by setup_handlers().
+///
+/// \param[out] old_sigmask The old signal mask to save via the
+/// \code oset \endcode argument with sigprocmask(2).
+static void
+mask_signals(sigset_t* old_sigmask)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ const int ret = ::sigprocmask(SIG_BLOCK, &mask, old_sigmask);
+ INV(ret != -1);
+}
+
+
+/// Resets the signal masking put in place by mask_signals().
+///
+/// \param[in] old_sigmask The old signal mask to restore via the
+/// \code set \endcode argument with sigprocmask(2).
+static void
+unmask_signals(sigset_t* old_sigmask)
+{
+ const int ret = ::sigprocmask(SIG_SETMASK, old_sigmask, NULL);
+ INV(ret != -1);
+}
+
+
+} // anonymous namespace
+
+
+/// Constructor that sets up the signal handlers.
+signals::interrupts_handler::interrupts_handler(void) :
+ _programmed(false)
+{
+ PRE(!interrupts_handler_active);
+ setup_handlers();
+ _programmed = true;
+ interrupts_handler_active = true;
+}
+
+
+/// Destructor that removes the signal handlers.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own.
+signals::interrupts_handler::~interrupts_handler(void)
+{
+ if (_programmed) {
+ LW("Destroying still-programmed signals::interrupts_handler object");
+ try {
+ unprogram();
+ } catch (const error& e) {
+ UNREACHABLE;
+ }
+ }
+}
+
+
+/// Unprograms all signals captured by the interrupts handler.
+///
+/// \throw system_error If the unprogramming of any signal fails.
+void
+signals::interrupts_handler::unprogram(void)
+{
+ PRE(_programmed);
+
+ // Modify the control variables first before unprogramming the handlers. If
+ // we fail to do the latter, we do not want to try again because we will not
+ // succeed (and we'll cause a crash due to failed preconditions).
+ _programmed = false;
+ interrupts_handler_active = false;
+
+ cleanup_handlers();
+ fired_signal = -1;
+}
+
+
+/// Constructor that sets up signal masking.
+signals::interrupts_inhibiter::interrupts_inhibiter(void)
+{
+ sigset_t old_sigmask;
+ mask_signals(&old_sigmask);
+ if (interrupts_inhibiter_active == 0) {
+ global_old_sigmask = old_sigmask;
+ }
+ ++interrupts_inhibiter_active;
+}
+
+
+/// Destructor that removes signal masking.
+signals::interrupts_inhibiter::~interrupts_inhibiter(void)
+{
+ if (interrupts_inhibiter_active > 1) {
+ --interrupts_inhibiter_active;
+ } else {
+ interrupts_inhibiter_active = false;
+ unmask_signals(&global_old_sigmask);
+ }
+}
+
+
+/// Checks if an interrupt has fired.
+///
+/// Calls to this function should be sprinkled in strategic places through the
+/// code protected by an interrupts_handler object.
+///
+/// Only one call to this function will raise an exception per signal received.
+/// This is to allow executing cleanup actions without reraising interrupt
+/// exceptions unless the user has fired another interrupt.
+///
+/// \throw interrupted_error If there has been an interrupt.
+void
+signals::check_interrupt(void)
+{
+ if (fired_signal != -1) {
+ const int original_fired_signal = fired_signal;
+ fired_signal = -1;
+ throw interrupted_error(original_fired_signal);
+ }
+}
+
+
+/// Registers a child process to be killed upon reception of an interrupt.
+///
+/// \pre Must be called with interrupts being inhibited. The caller must ensure
+/// that the call call to fork() and the addition of the PID happen atomically.
+///
+/// \param pid The PID of the child process. Must not have been yet regsitered.
+void
+signals::add_pid_to_kill(const pid_t pid)
+{
+ PRE(interrupts_inhibiter_active);
+ PRE(pids_to_kill.find(pid) == pids_to_kill.end());
+ pids_to_kill.insert(pid);
+}
+
+
+/// Unregisters a child process previously registered via add_pid_to_kill().
+///
+/// \pre Must be called with interrupts being inhibited. This is not necessary,
+/// but pushing this to the caller simplifies our logic and provides consistency
+/// with the add_pid_to_kill() call.
+///
+/// \param pid The PID of the child process. Must have been registered
+/// previously, and the process must have already been awaited for.
+void
+signals::remove_pid_to_kill(const pid_t pid)
+{
+ PRE(interrupts_inhibiter_active);
+ PRE(pids_to_kill.find(pid) != pids_to_kill.end());
+ pids_to_kill.erase(pid);
+}
diff --git a/utils/signals/interrupts.hpp b/utils/signals/interrupts.hpp
new file mode 100644
index 000000000000..b181114bb245
--- /dev/null
+++ b/utils/signals/interrupts.hpp
@@ -0,0 +1,83 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/interrupts.hpp
+/// Handling of interrupts.
+
+#if !defined(UTILS_SIGNALS_INTERRUPTS_HPP)
+#define UTILS_SIGNALS_INTERRUPTS_HPP
+
+#include "utils/signals/interrupts_fwd.hpp"
+
+#include <unistd.h>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+/// Provides a scope in which interrupts can be detected and handled.
+///
+/// This RAII-modeled object installs signal handler when instantiated and
+/// removes them upon destruction. While this object is active, the
+/// check_interrupt() free function can be used to determine if an interrupt has
+/// happened.
+class interrupts_handler : noncopyable {
+ /// Whether the interrupts are still programmed or not.
+ ///
+ /// Used by the destructor to prevent double-unprogramming when unprogram()
+ /// is explicitly called by the user.
+ bool _programmed;
+
+public:
+ interrupts_handler(void);
+ ~interrupts_handler(void);
+
+ void unprogram(void);
+};
+
+
+/// Disables interrupts while the object is alive.
+class interrupts_inhibiter : noncopyable {
+public:
+ interrupts_inhibiter(void);
+ ~interrupts_inhibiter(void);
+};
+
+
+void check_interrupt(void);
+
+void add_pid_to_kill(const pid_t);
+void remove_pid_to_kill(const pid_t);
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_INTERRUPTS_HPP)
diff --git a/utils/signals/interrupts_fwd.hpp b/utils/signals/interrupts_fwd.hpp
new file mode 100644
index 000000000000..e4dfe68d54e2
--- /dev/null
+++ b/utils/signals/interrupts_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/interrupts_fwd.hpp
+/// Forward declarations for utils/signals/interrupts.hpp
+
+#if !defined(UTILS_SIGNALS_INTERRUPTS_FWD_HPP)
+#define UTILS_SIGNALS_INTERRUPTS_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+class interrupts_handler;
+class interrupts_inhibiter;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_INTERRUPTS_FWD_HPP)
diff --git a/utils/signals/interrupts_test.cpp b/utils/signals/interrupts_test.cpp
new file mode 100644
index 000000000000..ef8758d8d5f1
--- /dev/null
+++ b/utils/signals/interrupts_test.cpp
@@ -0,0 +1,266 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/interrupts.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+/// Set to the signal that fired; -1 if none.
+static volatile int fired_signal = -1;
+
+
+/// Test handler for signals.
+///
+/// \post fired_signal is set to the signal that triggered the handler.
+///
+/// \param signo The signal that triggered the handler.
+static void
+signal_handler(const int signo)
+{
+ PRE(fired_signal == -1 || fired_signal == signo);
+ fired_signal = signo;
+}
+
+
+/// Child process that pauses waiting to be killed.
+static void
+pause_child(void)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ // We loop waiting for signals because we want the parent process to send us
+ // a SIGKILL that we cannot handle, not just any non-deadly signal.
+ for (;;) {
+ std::cerr << F("Waiting for any signal; pid=%s\n") % ::getpid();
+ ::sigsuspend(&mask);
+ std::cerr << F("Signal received; pid=%s\n") % ::getpid();
+ }
+}
+
+
+/// Checks that interrupts_handler() handles a particular signal.
+///
+/// This indirectly checks the check_interrupt() function, which is not part of
+/// the class but is tightly related.
+///
+/// \param signo The signal to check.
+/// \param explicit_unprogram Whether to call interrupts_handler::unprogram()
+/// explicitly before letting the object go out of scope.
+static void
+check_interrupts_handler(const int signo, const bool explicit_unprogram)
+{
+ fired_signal = -1;
+
+ signals::programmer test_handler(signo, signal_handler);
+
+ {
+ signals::interrupts_handler interrupts;
+
+ // No pending interrupts at first.
+ signals::check_interrupt();
+
+ // Send us an interrupt and check for it.
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_THROW_RE(signals::interrupted_error,
+ F("Interrupted by signal %s") % signo,
+ signals::check_interrupt());
+
+ // Interrupts should have been cleared now, so this should not throw.
+ signals::check_interrupt();
+
+ // Check to see if a second interrupt is detected.
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_THROW_RE(signals::interrupted_error,
+ F("Interrupted by signal %s") % signo,
+ signals::check_interrupt());
+
+ // And ensure the interrupt was cleared again.
+ signals::check_interrupt();
+
+ if (explicit_unprogram) {
+ interrupts.unprogram();
+ }
+ }
+
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_EQ(signo, fired_signal);
+
+ test_handler.unprogram();
+}
+
+
+/// Checks that interrupts_inhibiter() handles a particular signal.
+///
+/// \param signo The signal to check.
+static void
+check_interrupts_inhibiter(const int signo)
+{
+ signals::programmer test_handler(signo, signal_handler);
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+ {
+ signals::interrupts_inhibiter nested_inhibiter;
+ ::kill(::getpid(), signo);
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ }
+ ::kill(::getpid(), signo);
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ }
+ ATF_REQUIRE_EQ(signo, fired_signal);
+
+ test_handler.unprogram();
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sighup);
+ATF_TEST_CASE_BODY(interrupts_handler__sighup)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGHUP, true);
+ check_interrupts_handler(SIGHUP, false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigint);
+ATF_TEST_CASE_BODY(interrupts_handler__sigint)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGINT, true);
+ check_interrupts_handler(SIGINT, false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigterm);
+ATF_TEST_CASE_BODY(interrupts_handler__sigterm)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGTERM, true);
+ check_interrupts_handler(SIGTERM, false);
+}
+
+
+ATF_TEST_CASE(interrupts_handler__kill_children);
+ATF_TEST_CASE_HEAD(interrupts_handler__kill_children)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(interrupts_handler__kill_children)
+{
+ std::auto_ptr< process::child > child1(process::child::fork_files(
+ pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr")));
+ std::auto_ptr< process::child > child2(process::child::fork_files(
+ pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr")));
+
+ signals::interrupts_handler interrupts;
+
+ // Our children pause until the reception of a signal. Interrupting
+ // ourselves will cause the signal to be re-delivered to our children due to
+ // the interrupts_handler semantics. If this does not happen, the wait
+ // calls below would block indefinitely and cause our test to time out.
+ ::kill(::getpid(), SIGHUP);
+
+ const process::status status1 = child1->wait();
+ ATF_REQUIRE(status1.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, status1.termsig());
+ const process::status status2 = child2->wait();
+ ATF_REQUIRE(status2.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, status2.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigalrm);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigalrm)
+{
+ check_interrupts_inhibiter(SIGALRM);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sighup);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sighup)
+{
+ check_interrupts_inhibiter(SIGHUP);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigint);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigint)
+{
+ check_interrupts_inhibiter(SIGINT);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigterm);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigterm)
+{
+ check_interrupts_inhibiter(SIGTERM);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sighup);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigint);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigterm);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__kill_children);
+
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigalrm);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sighup);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigint);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigterm);
+}
diff --git a/utils/signals/misc.cpp b/utils/signals/misc.cpp
new file mode 100644
index 000000000000..b9eb1c402a28
--- /dev/null
+++ b/utils/signals/misc.cpp
@@ -0,0 +1,106 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/misc.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <signal.h>
+}
+
+#include <cerrno>
+#include <cstddef>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/signals/exceptions.hpp"
+
+namespace signals = utils::signals;
+
+
+/// Number of the last valid signal.
+const int utils::signals::last_signo = LAST_SIGNO;
+
+
+/// Resets a signal handler to its default behavior.
+///
+/// \param signo The number of the signal handler to reset.
+///
+/// \throw signals::system_error If there is a problem trying to reset the
+/// signal handler to its default behavior.
+void
+signals::reset(const int signo)
+{
+ struct ::sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ if (::sigaction(signo, &sa, NULL) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Failed to reset signal %s") % signo,
+ original_errno);
+ }
+}
+
+
+/// Resets all signals to their default handlers.
+///
+/// \return True if all signals could be reset properly; false otherwise.
+bool
+signals::reset_all(void)
+{
+ bool ok = true;
+
+ for (int signo = 1; signo <= signals::last_signo; ++signo) {
+ if (signo == SIGKILL || signo == SIGSTOP) {
+ // Don't attempt to reset immutable signals.
+ } else {
+ try {
+ signals::reset(signo);
+ } catch (const signals::error& e) {
+#if defined(SIGTHR)
+ if (signo == SIGTHR) {
+ // If FreeBSD's libthr is loaded, it prevents us from
+ // modifying SIGTHR (at least in 11.0-CURRENT as of
+ // 2015-01-28). Skip failures for this signal if they
+ // happen to avoid this corner case.
+ continue;
+ }
+#endif
+ LW(e.what());
+ ok = false;
+ }
+ }
+ }
+
+ return ok;
+}
diff --git a/utils/signals/misc.hpp b/utils/signals/misc.hpp
new file mode 100644
index 000000000000..ad3763feabc4
--- /dev/null
+++ b/utils/signals/misc.hpp
@@ -0,0 +1,49 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/misc.hpp
+/// Free functions and globals.
+
+#if !defined(UTILS_SIGNALS_MISC_HPP)
+#define UTILS_SIGNALS_MISC_HPP
+
+namespace utils {
+namespace signals {
+
+
+extern const int last_signo;
+
+
+void reset(const int);
+bool reset_all(void);
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_MISC_HPP)
diff --git a/utils/signals/misc_test.cpp b/utils/signals/misc_test.cpp
new file mode 100644
index 000000000000..76f36b0e5082
--- /dev/null
+++ b/utils/signals/misc_test.cpp
@@ -0,0 +1,133 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/misc.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/signals/exceptions.hpp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+static void program_reset_raise(void) UTILS_NORETURN;
+
+
+/// Body of a subprocess that tests the signals::reset function.
+///
+/// This function programs a signal to be ignored, then uses signal::reset to
+/// bring it back to its default handler and then delivers the signal to self.
+/// The default behavior of the signal is for the process to die, so this
+/// function should never return correctly (and thus the child process should
+/// always die due to a signal if all goes well).
+static void
+program_reset_raise(void)
+{
+ struct ::sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ if (::sigaction(SIGUSR1, &sa, NULL) == -1)
+ std::exit(EXIT_FAILURE);
+
+ signals::reset(SIGUSR1);
+ ::kill(::getpid(), SIGUSR1);
+
+ // Should not be reached, but we do not assert this condition because we
+ // want to exit cleanly if the signal does not abort our execution to let
+ // the parent easily know what happened.
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// Body of a subprocess that executes the signals::reset_all function.
+///
+/// The process exits with success if the function worked, or with a failure if
+/// an error is reported. No signals are tested.
+static void
+run_reset_all(void)
+{
+ const bool ok = signals::reset_all();
+ std::exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset__ok);
+ATF_TEST_CASE_BODY(reset__ok)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ program_reset_raise, fs::path("/dev/stdout"), fs::path("/dev/stderr"));
+ process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGUSR1, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset__invalid);
+ATF_TEST_CASE_BODY(reset__invalid)
+{
+ ATF_REQUIRE_THROW(signals::system_error, signals::reset(-1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset_all);
+ATF_TEST_CASE_BODY(reset_all)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ run_reset_all, fs::path("/dev/stdout"), fs::path("/dev/stderr"));
+ process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, reset__ok);
+ ATF_ADD_TEST_CASE(tcs, reset__invalid);
+ ATF_ADD_TEST_CASE(tcs, reset_all);
+}
diff --git a/utils/signals/programmer.cpp b/utils/signals/programmer.cpp
new file mode 100644
index 000000000000..c47d1cf85038
--- /dev/null
+++ b/utils/signals/programmer.cpp
@@ -0,0 +1,138 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/programmer.hpp"
+
+extern "C" {
+#include <signal.h>
+}
+
+#include <cerrno>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+
+
+namespace utils {
+namespace signals {
+
+
+/// Internal implementation for the signals::programmer class.
+struct programmer::impl : utils::noncopyable {
+ /// The number of the signal managed by this programmer.
+ int signo;
+
+ /// Whether the signal is currently programmed by us or not.
+ bool programmed;
+
+ /// The signal handler that we replaced; to be restored on unprogramming.
+ struct ::sigaction old_sa;
+
+ /// Initializes the internal implementation of the programmer.
+ ///
+ /// \param signo_ The signal number.
+ impl(const int signo_) :
+ signo(signo_),
+ programmed(false)
+ {
+ }
+};
+
+
+} // namespace signals
+} // namespace utils
+
+
+namespace signals = utils::signals;
+
+
+/// Programs a signal handler.
+///
+/// \param signo The signal for which to install the handler.
+/// \param handler The handler to install.
+///
+/// \throw signals::system_error If there is an error programming the signal.
+signals::programmer::programmer(const int signo, const handler_type handler) :
+ _pimpl(new impl(signo))
+{
+ struct ::sigaction sa;
+ sa.sa_handler = handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (::sigaction(_pimpl->signo, &sa, &_pimpl->old_sa) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Could not install handler for signal %s") %
+ _pimpl->signo, original_errno);
+ } else
+ _pimpl->programmed = true;
+}
+
+
+/// Destructor; unprograms the signal handler if still programmed.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own.
+signals::programmer::~programmer(void)
+{
+ if (_pimpl->programmed) {
+ LW("Destroying still-programmed signals::programmer object");
+ try {
+ unprogram();
+ } catch (const system_error& e) {
+ UNREACHABLE;
+ }
+ }
+}
+
+
+/// Unprograms the signal handler.
+///
+/// \pre The signal handler is programmed (i.e. this can only be called once).
+///
+/// \throw system_error If unprogramming the signal failed. If this happens,
+/// the signal is left programmed, this object forgets about the signal and
+/// therefore there is no way to restore the original handler.
+void
+signals::programmer::unprogram(void)
+{
+ PRE(_pimpl->programmed);
+
+ // If we fail, we don't want the destructor to attempt to unprogram the
+ // handler again, as it would result in a crash.
+ _pimpl->programmed = false;
+
+ if (::sigaction(_pimpl->signo, &_pimpl->old_sa, NULL) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Could not reset handler for signal %s") %
+ _pimpl->signo, original_errno);
+ }
+}
diff --git a/utils/signals/programmer.hpp b/utils/signals/programmer.hpp
new file mode 100644
index 000000000000..5ac5318f0bb9
--- /dev/null
+++ b/utils/signals/programmer.hpp
@@ -0,0 +1,63 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/programmer.hpp
+/// Provides the signals::programmer class.
+
+#if !defined(UTILS_SIGNALS_PROGRAMMER_HPP)
+#define UTILS_SIGNALS_PROGRAMMER_HPP
+
+#include "utils/signals/programmer_fwd.hpp"
+
+#include <memory>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+/// A RAII class to program signal handlers.
+class programmer : noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+public:
+ programmer(const int, const handler_type);
+ ~programmer(void);
+
+ void unprogram(void);
+};
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_PROGRAMMER_HPP)
diff --git a/utils/signals/programmer_fwd.hpp b/utils/signals/programmer_fwd.hpp
new file mode 100644
index 000000000000..55dfd34af2eb
--- /dev/null
+++ b/utils/signals/programmer_fwd.hpp
@@ -0,0 +1,49 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/programmer_fwd.hpp
+/// Forward declarations for utils/signals/programmer.hpp
+
+#if !defined(UTILS_SIGNALS_PROGRAMMER_FWD_HPP)
+#define UTILS_SIGNALS_PROGRAMMER_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+/// Function type for signal handlers.
+typedef void (*handler_type)(const int);
+
+
+class programmer;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_PROGRAMMER_FWD_HPP)
diff --git a/utils/signals/programmer_test.cpp b/utils/signals/programmer_test.cpp
new file mode 100644
index 000000000000..0e95f84974b1
--- /dev/null
+++ b/utils/signals/programmer_test.cpp
@@ -0,0 +1,140 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/programmer.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <atf-c++.hpp>
+
+#include "utils/sanity.hpp"
+
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+namespace sigchld {
+
+
+static bool happened_1;
+static bool happened_2;
+
+
+void handler_1(const int signo) {
+ PRE(signo == SIGCHLD);
+ happened_1 = true;
+}
+
+
+void handler_2(const int signo) {
+ PRE(signo == SIGCHLD);
+ happened_2 = true;
+}
+
+
+} // namespace sigchld
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(program_unprogram);
+ATF_TEST_CASE_BODY(program_unprogram)
+{
+ signals::programmer programmer(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+
+ programmer.unprogram();
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(scope);
+ATF_TEST_CASE_BODY(scope)
+{
+ {
+ signals::programmer programmer(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ }
+
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(nested);
+ATF_TEST_CASE_BODY(nested)
+{
+ signals::programmer programmer_1(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+
+ signals::programmer programmer_2(SIGCHLD, sigchld::handler_2);
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+ ATF_REQUIRE(sigchld::happened_2);
+
+ programmer_2.unprogram();
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+
+ programmer_1.unprogram();
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, program_unprogram);
+ ATF_ADD_TEST_CASE(tcs, scope);
+ ATF_ADD_TEST_CASE(tcs, nested);
+}
diff --git a/utils/signals/timer.cpp b/utils/signals/timer.cpp
new file mode 100644
index 000000000000..698b9835dc10
--- /dev/null
+++ b/utils/signals/timer.cpp
@@ -0,0 +1,547 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/timer.hpp"
+
+extern "C" {
+#include <sys/time.h>
+
+#include <signal.h>
+}
+
+#include <cerrno>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "utils/datetime.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/interrupts.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace datetime = utils::datetime;
+namespace signals = utils::signals;
+
+using utils::none;
+using utils::optional;
+
+namespace {
+
+
+static void sigalrm_handler(const int);
+
+
+/// Calls setitimer(2) with exception-based error reporting.
+///
+/// This does not currently support intervals.
+///
+/// \param delta The time to the first activation of the programmed timer.
+/// \param old_timeval If not NULL, pointer to a timeval into which to store the
+/// existing system timer.
+///
+/// \throw system_error If the call to setitimer(2) fails.
+static void
+safe_setitimer(const datetime::delta& delta, itimerval* old_timeval)
+{
+ ::itimerval timeval;
+ timerclear(&timeval.it_interval);
+ timeval.it_value.tv_sec = delta.seconds;
+ timeval.it_value.tv_usec = delta.useconds;
+
+ if (::setitimer(ITIMER_REAL, &timeval, old_timeval) == -1) {
+ const int original_errno = errno;
+ throw signals::system_error("Failed to program system's interval timer",
+ original_errno);
+ }
+}
+
+
+/// Deadline scheduler for all user timers on top of the unique system timer.
+class global_state : utils::noncopyable {
+ /// Collection of active timers.
+ ///
+ /// Because this is a collection of pointers, all timers are guaranteed to
+ /// be unique, and we want all of these pointers to be valid.
+ typedef std::set< signals::timer* > timers_set;
+
+ /// Sequence of ordered timers.
+ typedef std::vector< signals::timer* > timers_vector;
+
+ /// Collection of active timestamps by their activation timestamp.
+ ///
+ /// This collection is ordered intentionally so that it can be scanned
+ /// sequentially to find either expired or expiring-now timers.
+ typedef std::map< datetime::timestamp, timers_set > timers_by_timestamp_map;
+
+ /// The original timer before any timer was programmed.
+ ::itimerval _old_timeval;
+
+ /// Programmer for the SIGALRM handler.
+ std::auto_ptr< signals::programmer > _sigalrm_programmer;
+
+ /// Time of the current activation of the timer.
+ datetime::timestamp _timer_activation;
+
+ /// Mapping of all active timers using their timestamp as the key.
+ timers_by_timestamp_map _all_timers;
+
+ /// Adds a timer to the _all_timers map.
+ ///
+ /// \param timer The timer to add.
+ void
+ add_to_all_timers(signals::timer* timer)
+ {
+ timers_set& timers = _all_timers[timer->when()];
+ INV(timers.find(timer) == timers.end());
+ timers.insert(timer);
+ }
+
+ /// Removes a timer from the _all_timers map.
+ ///
+ /// This ensures that empty vectors are removed from _all_timers if the
+ /// removal of the timer causes its bucket to be emptied.
+ ///
+ /// \param timer The timer to remove.
+ void
+ remove_from_all_timers(signals::timer* timer)
+ {
+ // We may not find the timer in _all_timers if the timer has fired,
+ // because fire() took it out from the map.
+ timers_by_timestamp_map::iterator iter = _all_timers.find(
+ timer->when());
+ if (iter != _all_timers.end()) {
+ timers_set& timers = (*iter).second;
+ INV(timers.find(timer) != timers.end());
+ timers.erase(timer);
+ if (timers.empty()) {
+ _all_timers.erase(iter);
+ }
+ }
+ }
+
+ /// Calculates all timers to execute at this timestamp.
+ ///
+ /// \param now The current timestamp.
+ ///
+ /// \post _all_timers is updated to contain only the timers that are
+ /// strictly in the future.
+ ///
+ /// \return A sequence of valid timers that need to be invoked in the order
+ /// of activation. These are all previously registered timers with
+ /// activations in the past.
+ timers_vector
+ compute_timers_to_run_and_prune_old(
+ const datetime::timestamp& now,
+ const signals::interrupts_inhibiter& /* inhibiter */)
+ {
+ timers_vector to_run;
+
+ timers_by_timestamp_map::iterator iter = _all_timers.begin();
+ while (iter != _all_timers.end() && (*iter).first <= now) {
+ const timers_set& timers = (*iter).second;
+ to_run.insert(to_run.end(), timers.begin(), timers.end());
+
+ // Remove expired entries here so that we can always assume that
+ // the first entry in all_timers corresponds to the next
+ // activation.
+ const timers_by_timestamp_map::iterator previous_iter = iter;
+ ++iter;
+ _all_timers.erase(previous_iter);
+ }
+
+ return to_run;
+ }
+
+ /// Adjusts the global system timer to point to the next activation.
+ ///
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ void
+ reprogram_system_timer(
+ const datetime::timestamp& now,
+ const signals::interrupts_inhibiter& /* inhibiter */)
+ {
+ if (_all_timers.empty()) {
+ // Nothing to do. We can reach this case if all the existing timers
+ // are in the past and they all fired. Just ignore the request and
+ // leave the global timer as is.
+ return;
+ }
+
+ // While fire() prunes old entries from the list of timers, it is
+ // possible for this routine to run with "expired" timers (i.e. timers
+ // whose deadline lies in the past but that have not yet fired for
+ // whatever reason that is out of our control) in the list. We have to
+ // iterate until we find the next activation instead of assuming that
+ // the first entry represents the desired value.
+ timers_by_timestamp_map::const_iterator iter = _all_timers.begin();
+ PRE(!(*iter).second.empty());
+ datetime::timestamp next = (*iter).first;
+ while (next < now) {
+ ++iter;
+ if (iter == _all_timers.end()) {
+ // Nothing to do. We can reach this case if all the existing
+ // timers are in the past but they have not yet fired.
+ return;
+ }
+ PRE(!(*iter).second.empty());
+ next = (*iter).first;
+ }
+
+ if (next < _timer_activation || now > _timer_activation) {
+ INV(next >= now);
+ const datetime::delta delta = next - now;
+ LD(F("Reprogramming timer; firing on %s; now is %s") % next % now);
+ safe_setitimer(delta, NULL);
+ _timer_activation = next;
+ }
+ }
+
+public:
+ /// Programs the first timer.
+ ///
+ /// The programming of the first timer involves setting up the SIGALRM
+ /// handler and installing a timer handler for the first time, which in turn
+ /// involves keeping track of the old handlers so that we can restore them.
+ ///
+ /// \param timer The timer being programmed.
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ global_state(signals::timer* timer, const datetime::timestamp& now) :
+ _timer_activation(timer->when())
+ {
+ PRE(now < timer->when());
+
+ signals::interrupts_inhibiter inhibiter;
+
+ const datetime::delta delta = timer->when() - now;
+ LD(F("Installing first timer; firing on %s; now is %s") %
+ timer->when() % now);
+
+ _sigalrm_programmer.reset(
+ new signals::programmer(SIGALRM, sigalrm_handler));
+ try {
+ safe_setitimer(delta, &_old_timeval);
+ _timer_activation = timer->when();
+ add_to_all_timers(timer);
+ } catch (...) {
+ _sigalrm_programmer.reset(NULL);
+ throw;
+ }
+ }
+
+ /// Unprograms all timers.
+ ///
+ /// This clears the global system timer and unsets the SIGALRM handler.
+ ~global_state(void)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ LD("Unprogramming all timers");
+
+ if (::setitimer(ITIMER_REAL, &_old_timeval, NULL) == -1) {
+ UNREACHABLE_MSG("Failed to restore original timer");
+ }
+
+ _sigalrm_programmer->unprogram();
+ _sigalrm_programmer.reset(NULL);
+ }
+
+ /// Programs a new timer, possibly adjusting the global system timer.
+ ///
+ /// Programming any timer other than the first one only involves reloading
+ /// the existing timer, not backing up the previous handler nor installing a
+ /// handler for SIGALRM.
+ ///
+ /// \param timer The timer being programmed.
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ void
+ program_new(signals::timer* timer, const datetime::timestamp& now)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ add_to_all_timers(timer);
+ reprogram_system_timer(now, inhibiter);
+ }
+
+ /// Unprograms a timer.
+ ///
+ /// This removes the timer from the global state and reprograms the global
+ /// system timer if necessary.
+ ///
+ /// \param timer The timer to unprogram.
+ ///
+ /// \return True if the system interval timer has been reprogrammed to
+ /// another future timer; false if there are no more active timers.
+ bool
+ unprogram(signals::timer* timer)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ LD(F("Unprogramming timer; previously firing on %s") % timer->when());
+
+ remove_from_all_timers(timer);
+ if (_all_timers.empty()) {
+ return false;
+ } else {
+ reprogram_system_timer(datetime::timestamp::now(), inhibiter);
+ return true;
+ }
+ }
+
+ /// Executes active timers.
+ ///
+ /// Active timers are all those that fire on or before 'now'.
+ ///
+ /// \param now The current time.
+ void
+ fire(const datetime::timestamp& now)
+ {
+ timers_vector to_run;
+ {
+ signals::interrupts_inhibiter inhibiter;
+ to_run = compute_timers_to_run_and_prune_old(now, inhibiter);
+ reprogram_system_timer(now, inhibiter);
+ }
+
+ for (timers_vector::iterator iter = to_run.begin();
+ iter != to_run.end(); ++iter) {
+ signals::detail::invoke_do_fired(*iter);
+ }
+ }
+};
+
+
+/// Unique instance of the global state.
+static std::auto_ptr< global_state > globals;
+
+
+/// SIGALRM handler for the timer implementation.
+///
+/// \param signo The signal received; must be SIGALRM.
+static void
+sigalrm_handler(const int signo)
+{
+ PRE(signo == SIGALRM);
+ globals->fire(datetime::timestamp::now());
+}
+
+
+} // anonymous namespace
+
+
+/// Indirection to invoke the private do_fired() method of a timer.
+///
+/// \param timer The timer on which to run do_fired().
+void
+utils::signals::detail::invoke_do_fired(timer* timer)
+{
+ timer->do_fired();
+}
+
+
+/// Internal implementation for the timer.
+///
+/// We assume that there is a 1-1 mapping between timer objects and impl
+/// objects. If this assumption breaks, then the rest of the code in this
+/// module breaks as well because we use pointers to the parent timer as the
+/// identifier of the timer.
+struct utils::signals::timer::impl : utils::noncopyable {
+ /// Timestamp when this timer is expected to fire.
+ ///
+ /// Note that the timer might be processed after this timestamp, so users of
+ /// this field need to check for timers that fire on or before the
+ /// activation time.
+ datetime::timestamp when;
+
+ /// True until unprogram() is called.
+ bool programmed;
+
+ /// Whether this timer has fired already or not.
+ ///
+ /// This is updated from an interrupt context, hence why it is marked
+ /// volatile.
+ volatile bool fired;
+
+ /// Constructor.
+ ///
+ /// \param when_ Timestamp when this timer is expected to fire.
+ impl(const datetime::timestamp& when_) :
+ when(when_), programmed(true), fired(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void) {
+ }
+};
+
+
+/// Constructor; programs a run-once timer.
+///
+/// This programs the global timer and signal handler if this is the first timer
+/// being installed. Otherwise, reprograms the global timer if this timer
+/// expires earlier than all other active timers.
+///
+/// \param delta The time until the timer fires.
+signals::timer::timer(const datetime::delta& delta)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ const datetime::timestamp now = datetime::timestamp::now();
+ _pimpl.reset(new impl(now + delta));
+ if (globals.get() == NULL) {
+ globals.reset(new global_state(this, now));
+ } else {
+ globals->program_new(this, now);
+ }
+}
+
+
+/// Destructor; unprograms the timer if still programmed.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own. This is
+/// extremely important because, otherwise, expired timers will never run!
+signals::timer::~timer(void)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ if (_pimpl->programmed) {
+ LW("Auto-destroying still-programmed signals::timer object");
+ try {
+ unprogram();
+ } catch (const system_error& e) {
+ UNREACHABLE;
+ }
+ }
+
+ if (!_pimpl->fired) {
+ const datetime::timestamp now = datetime::timestamp::now();
+ if (now > _pimpl->when) {
+ LW("Expired timer never fired; the code never called unprogram()!");
+ }
+ }
+}
+
+
+/// Returns the time of the timer activation.
+///
+/// \return A timestamp that has no relation to the current time (i.e. can be in
+/// the future or in the past) nor the timer's activation status.
+const datetime::timestamp&
+signals::timer::when(void) const
+{
+ return _pimpl->when;
+}
+
+
+/// Callback for the SIGALRM handler when this timer expires.
+///
+/// \warning This is executed from a signal handler context without signals
+/// inhibited. See signal(7) for acceptable system calls.
+void
+signals::timer::do_fired(void)
+{
+ PRE(!_pimpl->fired);
+ _pimpl->fired = true;
+ callback();
+}
+
+
+/// User-provided callback to run when the timer expires.
+///
+/// The default callback does nothing. We record the activation of the timer
+/// separately, which may be appropriate in the majority of the cases.
+///
+/// \warning This is executed from a signal handler context without signals
+/// inhibited. See signal(7) for acceptable system calls.
+void
+signals::timer::callback(void)
+{
+ // Intentionally left blank.
+}
+
+
+/// Checks whether the timer has fired already or not.
+///
+/// \return Returns true if the timer has fired.
+bool
+signals::timer::fired(void) const
+{
+ return _pimpl->fired;
+}
+
+
+/// Unprograms the timer.
+///
+/// \pre The timer is programmed (i.e. this can only be called once).
+///
+/// \post If the timer never fired asynchronously because the signal delivery
+/// did not arrive on time, make sure we invoke the timer's callback here.
+///
+/// \throw system_error If unprogramming the timer failed.
+void
+signals::timer::unprogram(void)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ if (!_pimpl->programmed) {
+ // We cannot assert that the timer is not programmed because it might
+ // have been unprogrammed asynchronously between the time we called
+ // unprogram() and the time we reach this. Simply return in this case.
+ LD("Called unprogram on already-unprogrammed timer; possibly just "
+ "a race");
+ return;
+ }
+
+ if (!globals->unprogram(this)) {
+ globals.reset(NULL);
+ }
+ _pimpl->programmed = false;
+
+ // Handle the case where the timer has expired before we ever got its
+ // corresponding signal. Do so by invoking its callback now.
+ if (!_pimpl->fired) {
+ const datetime::timestamp now = datetime::timestamp::now();
+ if (now > _pimpl->when) {
+ LW(F("Firing expired timer on destruction (was to fire on %s)") %
+ _pimpl->when);
+ do_fired();
+ }
+ }
+}
diff --git a/utils/signals/timer.hpp b/utils/signals/timer.hpp
new file mode 100644
index 000000000000..1174effe2b48
--- /dev/null
+++ b/utils/signals/timer.hpp
@@ -0,0 +1,86 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/timer.hpp
+/// Multiprogrammed support for timers.
+///
+/// The timer module and class implement a mechanism to program multiple timers
+/// concurrently by using a deadline scheduler and leveraging the "single timer"
+/// features of the underlying operating system.
+
+#if !defined(UTILS_SIGNALS_TIMER_HPP)
+#define UTILS_SIGNALS_TIMER_HPP
+
+#include "utils/signals/timer_fwd.hpp"
+
+#include <memory>
+
+#include "utils/datetime_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+namespace detail {
+void invoke_do_fired(timer*);
+} // namespace detail
+
+
+/// Individual timer.
+///
+/// Asynchronously executes its callback() method, which can be overridden by
+/// subclasses, when the timeout given at construction expires.
+class timer : noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ friend void detail::invoke_do_fired(timer*);
+ void do_fired(void);
+
+protected:
+ virtual void callback(void);
+
+public:
+ timer(const utils::datetime::delta&);
+ virtual ~timer(void);
+
+ const utils::datetime::timestamp& when(void) const;
+
+ bool fired(void) const;
+
+ void unprogram(void);
+};
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_TIMER_HPP)
diff --git a/utils/signals/timer_fwd.hpp b/utils/signals/timer_fwd.hpp
new file mode 100644
index 000000000000..a3cf3e205d70
--- /dev/null
+++ b/utils/signals/timer_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/timer_fwd.hpp
+/// Forward declarations for utils/signals/timer.hpp
+
+#if !defined(UTILS_SIGNALS_TIMER_FWD_HPP)
+#define UTILS_SIGNALS_TIMER_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+class timer;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_TIMER_FWD_HPP)
diff --git a/utils/signals/timer_test.cpp b/utils/signals/timer_test.cpp
new file mode 100644
index 000000000000..61e9cac6b088
--- /dev/null
+++ b/utils/signals/timer_test.cpp
@@ -0,0 +1,426 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/timer.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstddef>
+#include <iostream>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/containers.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/signals/interrupts.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace datetime = utils::datetime;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+/// A timer that inserts an element into a vector on activation.
+class delayed_inserter : public signals::timer {
+ /// Vector into which to insert the element.
+ std::vector< int >& _destination;
+
+ /// Element to insert into _destination on activation.
+ const int _item;
+
+ /// Timer activation callback.
+ void
+ callback(void)
+ {
+ signals::interrupts_inhibiter inhibiter;
+ _destination.push_back(_item);
+ }
+
+public:
+ /// Constructor.
+ ///
+ /// \param delta Time to the timer activation.
+ /// \param destination Vector into which to insert the element.
+ /// \param item Element to insert into destination on activation.
+ delayed_inserter(const datetime::delta& delta,
+ std::vector< int >& destination, const int item) :
+ signals::timer(delta), _destination(destination), _item(item)
+ {
+ }
+};
+
+
+/// Signal handler that does nothing.
+static void
+null_handler(const int /* signo */)
+{
+}
+
+
+/// Waits for the activation of all given timers.
+///
+/// \param timers Pointers to all the timers to wait for.
+static void
+wait_timers(const std::vector< signals::timer* >& timers)
+{
+ std::size_t n_fired, old_n_fired = 0;
+ do {
+ n_fired = 0;
+ for (std::vector< signals::timer* >::const_iterator
+ iter = timers.begin(); iter != timers.end(); ++iter) {
+ const signals::timer* timer = *iter;
+ if (timer->fired())
+ ++n_fired;
+ }
+ if (old_n_fired < n_fired) {
+ std::cout << "Waiting; " << n_fired << " timers fired so far\n";
+ old_n_fired = n_fired;
+ }
+ ::usleep(100);
+ } while (n_fired < timers.size());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE(program_seconds);
+ATF_TEST_CASE_HEAD(program_seconds)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(program_seconds)
+{
+ signals::timer timer(datetime::delta(1, 0));
+ ATF_REQUIRE(!timer.fired());
+ while (!timer.fired())
+ ::usleep(1000);
+}
+
+
+ATF_TEST_CASE(program_useconds);
+ATF_TEST_CASE_HEAD(program_useconds)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(program_useconds)
+{
+ signals::timer timer(datetime::delta(0, 500000));
+ ATF_REQUIRE(!timer.fired());
+ while (!timer.fired())
+ ::usleep(1000);
+}
+
+
+ATF_TEST_CASE(multiprogram_ordered);
+ATF_TEST_CASE_HEAD(multiprogram_ordered)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_ordered)
+{
+ static const std::size_t n_timers = 100;
+
+ std::vector< signals::timer* > timers;
+ std::vector< int > items, exp_items;
+
+ const int initial_delay_ms = 1000000;
+ for (std::size_t i = 0; i < n_timers; ++i) {
+ exp_items.push_back(i);
+
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, initial_delay_ms + (i + 1) * 10000),
+ items, i));
+ ATF_REQUIRE(!timers[i]->fired());
+ }
+
+ wait_timers(timers);
+
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_reorder_next_activations);
+ATF_TEST_CASE_HEAD(multiprogram_reorder_next_activations)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_reorder_next_activations)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation in between.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 150000), items, 4));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ exp_items.push_back(1);
+ exp_items.push_back(4);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_and_cancel_some);
+ATF_TEST_CASE_HEAD(multiprogram_and_cancel_some)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_and_cancel_some)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+
+ // Timer with an activation in between.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 150000), items, 4));
+
+ // Cancel the first timer to reprogram next activation.
+ timers[1]->unprogram(); delete timers[1]; timers.erase(timers.begin() + 1);
+
+ // Cancel another timer without reprogramming next activation.
+ timers[2]->unprogram(); delete timers[2]; timers.erase(timers.begin() + 2);
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(1);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_and_expire_before_activations);
+ATF_TEST_CASE_HEAD(multiprogram_and_expire_before_activations)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_and_expire_before_activations)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ ::sleep(1);
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+
+ ::sleep(1);
+ }
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ exp_items.push_back(1);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(expire_before_firing);
+ATF_TEST_CASE_HEAD(expire_before_firing)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(expire_before_firing)
+{
+ std::vector< int > items;
+
+ // The code below causes a signal to go pending. Make sure we ignore it
+ // when we unblock signals.
+ signals::programmer sigalrm(SIGALRM, null_handler);
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ delayed_inserter* timer = new delayed_inserter(
+ datetime::delta(0, 1000), items, 1234);
+ ::sleep(1);
+ // Interrupts are inhibited so we never got a chance to execute the
+ // timer before it was destroyed. However, the handler should run
+ // regardless at some point, possibly during deletion.
+ timer->unprogram();
+ delete timer;
+ }
+
+ std::vector< int > exp_items;
+ exp_items.push_back(1234);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(reprogram_from_scratch);
+ATF_TEST_CASE_HEAD(reprogram_from_scratch)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(reprogram_from_scratch)
+{
+ std::vector< int > items;
+
+ delayed_inserter* timer1 = new delayed_inserter(
+ datetime::delta(0, 100000), items, 1);
+ timer1->unprogram(); delete timer1;
+
+ // All constructed timers are now dead, so the interval timer should have
+ // been reprogrammed. Let's start over.
+
+ delayed_inserter* timer2 = new delayed_inserter(
+ datetime::delta(0, 200000), items, 2);
+ while (!timer2->fired())
+ ::usleep(1000);
+ timer2->unprogram(); delete timer2;
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(unprogram);
+ATF_TEST_CASE_HEAD(unprogram)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(unprogram)
+{
+ signals::timer timer(datetime::delta(0, 500000));
+ timer.unprogram();
+ usleep(500000);
+ ATF_REQUIRE(!timer.fired());
+}
+
+
+ATF_TEST_CASE(infinitesimal);
+ATF_TEST_CASE_HEAD(infinitesimal)
+{
+ set_md_var("descr", "Ensure that the ordering in which the signal, the "
+ "timer and the global state are programmed is correct; do so "
+ "by setting an extremely small delay for the timer hoping that "
+ "it can trigger such conditions");
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(infinitesimal)
+{
+ const std::size_t rounds = 100;
+ const std::size_t exp_good = 90;
+
+ std::size_t good = 0;
+ for (std::size_t i = 0; i < rounds; i++) {
+ signals::timer timer(datetime::delta(0, 1));
+
+ // From the setitimer(2) documentation:
+ //
+ // Time values smaller than the resolution of the system clock are
+ // rounded up to this resolution (typically 10 milliseconds).
+ //
+ // We don't know what this resolution is but we must wait for longer
+ // than we programmed; do a rough guess and hope it is good. This may
+ // be obviously wrong and thus lead to mysterious test failures in some
+ // systems, hence why we only expect a percentage of successes below.
+ // Still, we can fail...
+ ::usleep(1000);
+
+ if (timer.fired())
+ ++good;
+ timer.unprogram();
+ }
+ std::cout << F("Ran %s tests, %s passed; threshold is %s\n")
+ % rounds % good % exp_good;
+ ATF_REQUIRE(good >= exp_good);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, program_seconds);
+ ATF_ADD_TEST_CASE(tcs, program_useconds);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_ordered);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_reorder_next_activations);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_and_cancel_some);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_and_expire_before_activations);
+ ATF_ADD_TEST_CASE(tcs, expire_before_firing);
+ ATF_ADD_TEST_CASE(tcs, reprogram_from_scratch);
+ ATF_ADD_TEST_CASE(tcs, unprogram);
+ ATF_ADD_TEST_CASE(tcs, infinitesimal);
+}
diff --git a/utils/sqlite/Kyuafile b/utils/sqlite/Kyuafile
new file mode 100644
index 000000000000..47a8b95dac92
--- /dev/null
+++ b/utils/sqlite/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="c_gate_test"}
+atf_test_program{name="database_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="statement_test"}
+atf_test_program{name="transaction_test"}
diff --git a/utils/sqlite/Makefile.am.inc b/utils/sqlite/Makefile.am.inc
new file mode 100644
index 000000000000..6064a641c14f
--- /dev/null
+++ b/utils/sqlite/Makefile.am.inc
@@ -0,0 +1,82 @@
+# Copyright 2011 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(SQLITE3_CFLAGS)
+UTILS_LIBS += $(SQLITE3_LIBS)
+
+libutils_a_CPPFLAGS += $(SQLITE3_CFLAGS)
+libutils_a_SOURCES += utils/sqlite/c_gate.cpp
+libutils_a_SOURCES += utils/sqlite/c_gate.hpp
+libutils_a_SOURCES += utils/sqlite/c_gate_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/database.cpp
+libutils_a_SOURCES += utils/sqlite/database.hpp
+libutils_a_SOURCES += utils/sqlite/database_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/exceptions.cpp
+libutils_a_SOURCES += utils/sqlite/exceptions.hpp
+libutils_a_SOURCES += utils/sqlite/statement.cpp
+libutils_a_SOURCES += utils/sqlite/statement.hpp
+libutils_a_SOURCES += utils/sqlite/statement_fwd.hpp
+libutils_a_SOURCES += utils/sqlite/statement.ipp
+libutils_a_SOURCES += utils/sqlite/transaction.cpp
+libutils_a_SOURCES += utils/sqlite/transaction.hpp
+libutils_a_SOURCES += utils/sqlite/transaction_fwd.hpp
+
+if WITH_ATF
+tests_utils_sqlitedir = $(pkgtestsdir)/utils/sqlite
+
+tests_utils_sqlite_DATA = utils/sqlite/Kyuafile
+EXTRA_DIST += $(tests_utils_sqlite_DATA)
+
+tests_utils_sqlite_PROGRAMS = utils/sqlite/c_gate_test
+utils_sqlite_c_gate_test_SOURCES = utils/sqlite/c_gate_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_c_gate_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_c_gate_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/database_test
+utils_sqlite_database_test_SOURCES = utils/sqlite/database_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_database_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_database_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/exceptions_test
+utils_sqlite_exceptions_test_SOURCES = utils/sqlite/exceptions_test.cpp
+utils_sqlite_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/statement_test
+utils_sqlite_statement_test_SOURCES = utils/sqlite/statement_test.cpp \
+ utils/sqlite/test_utils.hpp
+utils_sqlite_statement_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_statement_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_sqlite_PROGRAMS += utils/sqlite/transaction_test
+utils_sqlite_transaction_test_SOURCES = utils/sqlite/transaction_test.cpp
+utils_sqlite_transaction_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sqlite_transaction_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/sqlite/c_gate.cpp b/utils/sqlite/c_gate.cpp
new file mode 100644
index 000000000000..e89ac5332ea0
--- /dev/null
+++ b/utils/sqlite/c_gate.cpp
@@ -0,0 +1,83 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/c_gate.hpp"
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/database.hpp"
+
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+
+
+/// Creates a new gateway to an existing C++ SQLite database.
+///
+/// \param database_ The database to connect to. This object must remain alive
+/// while the newly-constructed database_c_gate is alive.
+sqlite::database_c_gate::database_c_gate(database& database_) :
+ _database(database_)
+{
+}
+
+
+/// Destructor.
+///
+/// Destroying this object has no implications on the life cycle of the SQLite
+/// database. Only the corresponding database object controls when the SQLite 3
+/// database is closed.
+sqlite::database_c_gate::~database_c_gate(void)
+{
+}
+
+
+/// Creates a C++ database for a C SQLite 3 database.
+///
+/// \warning The created database object does NOT own the C database. You must
+/// take care to properly destroy the input sqlite3 when you are done with it to
+/// not leak resources.
+///
+/// \param raw_database The raw database to wrap temporarily.
+///
+/// \return The wrapped database without strong ownership on the input database.
+sqlite::database
+sqlite::database_c_gate::connect(::sqlite3* raw_database)
+{
+ return database(none, static_cast< void* >(raw_database), false);
+}
+
+
+/// Returns the C native SQLite 3 database.
+///
+/// \return A native sqlite3 object holding the SQLite 3 C API database.
+::sqlite3*
+sqlite::database_c_gate::c_database(void)
+{
+ return static_cast< ::sqlite3* >(_database.raw_database());
+}
diff --git a/utils/sqlite/c_gate.hpp b/utils/sqlite/c_gate.hpp
new file mode 100644
index 000000000000..0ca9d79c4815
--- /dev/null
+++ b/utils/sqlite/c_gate.hpp
@@ -0,0 +1,74 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file c_gate.hpp
+/// Provides direct access to the C state of the SQLite wrappers.
+
+#if !defined(UTILS_SQLITE_C_GATE_HPP)
+#define UTILS_SQLITE_C_GATE_HPP
+
+#include "utils/sqlite/c_gate_fwd.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Gateway to the raw C database of SQLite 3.
+///
+/// This class provides a mechanism to muck with the internals of the database
+/// wrapper class.
+///
+/// \warning The use of this class is discouraged. By using this class, you are
+/// entering the world of unsafety. Anything you do through the objects exposed
+/// through this class will not be controlled by RAII patterns not validated in
+/// any other way, so you can end up corrupting the SQLite 3 state and later get
+/// crashes on otherwise perfectly-valid C++ code.
+class database_c_gate {
+ /// The C++ database that this class wraps.
+ database& _database;
+
+public:
+ database_c_gate(database&);
+ ~database_c_gate(void);
+
+ static database connect(::sqlite3*);
+
+ ::sqlite3* c_database(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_C_GATE_HPP)
diff --git a/utils/sqlite/c_gate_fwd.hpp b/utils/sqlite/c_gate_fwd.hpp
new file mode 100644
index 000000000000..771efeeff463
--- /dev/null
+++ b/utils/sqlite/c_gate_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/c_gate_fwd.hpp
+/// Forward declarations for utils/sqlite/c_gate.hpp
+
+#if !defined(UTILS_SQLITE_C_GATE_FWD_HPP)
+#define UTILS_SQLITE_C_GATE_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class database_c_gate;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_C_GATE_FWD_HPP)
diff --git a/utils/sqlite/c_gate_test.cpp b/utils/sqlite/c_gate_test.cpp
new file mode 100644
index 000000000000..edf46f76c902
--- /dev/null
+++ b/utils/sqlite/c_gate_test.cpp
@@ -0,0 +1,96 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/c_gate.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/test_utils.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(connect);
+ATF_TEST_CASE_BODY(connect)
+{
+ ::sqlite3* raw_db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2(":memory:", &raw_db,
+ SQLITE_OPEN_READWRITE, NULL));
+ {
+ sqlite::database database = sqlite::database_c_gate::connect(raw_db);
+ create_test_table(raw(database));
+ }
+ // If the wrapper object has closed the SQLite 3 database, we will misbehave
+ // here either by crashing or not finding our test table.
+ verify_test_table(raw_db);
+ ::sqlite3_close(raw_db);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(c_database);
+ATF_TEST_CASE_BODY(c_database)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ {
+ sqlite::database_c_gate gate(db);
+ ::sqlite3* raw_db = gate.c_database();
+ verify_test_table(raw_db);
+ }
+}
+
+
+ATF_TEST_CASE(database__db_filename);
+ATF_TEST_CASE_HEAD(database__db_filename)
+{
+ set_md_var("descr", "The current implementation of db_filename() has no "
+ "means to access the filename of a database connected to a raw "
+ "sqlite3 object");
+}
+ATF_TEST_CASE_BODY(database__db_filename)
+{
+ ::sqlite3* raw_db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2(
+ "test.db", &raw_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));
+
+ sqlite::database database = sqlite::database_c_gate::connect(raw_db);
+ ATF_REQUIRE(!database.db_filename());
+ ::sqlite3_close(raw_db);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, c_database);
+ ATF_ADD_TEST_CASE(tcs, connect);
+ ATF_ADD_TEST_CASE(tcs, database__db_filename);
+}
diff --git a/utils/sqlite/database.cpp b/utils/sqlite/database.cpp
new file mode 100644
index 000000000000..41935c3b017d
--- /dev/null
+++ b/utils/sqlite/database.cpp
@@ -0,0 +1,328 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/database.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <cstring>
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+#include "utils/sqlite/transaction.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+using utils::optional;
+
+
+/// Internal implementation for sqlite::database.
+struct utils::sqlite::database::impl : utils::noncopyable {
+ /// Path to the database as seen at construction time.
+ optional< fs::path > db_filename;
+
+ /// The SQLite 3 internal database.
+ ::sqlite3* db;
+
+ /// Whether we own the database or not (to decide if we close it).
+ bool owned;
+
+ /// Constructor.
+ ///
+ /// \param db_filename_ The path to the database as seen at construction
+ /// time, if any, or none for in-memory databases. We should use
+ /// sqlite3_db_filename instead, but this function appeared in 3.7.10
+ /// and Ubuntu 12.04 LTS (which we support for Travis CI builds as of
+ /// 2015-07-07) ships with 3.7.9.
+ /// \param db_ The SQLite internal database.
+ /// \param owned_ Whether this object owns the db_ object or not. If it
+ /// does, the internal db_ will be released during destruction.
+ impl(optional< fs::path > db_filename_, ::sqlite3* db_, const bool owned_) :
+ db_filename(db_filename_), db(db_), owned(owned_)
+ {
+ }
+
+ /// Destructor.
+ ///
+ /// It is important to keep this as part of the 'impl' class instead of the
+ /// container class. The 'impl' class is destroyed exactly once (because it
+ /// is managed by a shared_ptr) and thus releasing the resources here is
+ /// OK. However, the container class is potentially released many times,
+ /// which means that we would be double-freeing the internal object and
+ /// reusing invalid data.
+ ~impl(void)
+ {
+ if (owned && db != NULL)
+ close();
+ }
+
+ /// Exception-safe version of sqlite3_open_v2.
+ ///
+ /// \param file The path to the database file to be opened.
+ /// \param flags The flags to be passed to the open routine.
+ ///
+ /// \return The opened database.
+ ///
+ /// \throw std::bad_alloc If there is not enough memory to open the
+ /// database.
+ /// \throw api_error If there is any problem opening the database.
+ static ::sqlite3*
+ safe_open(const char* file, const int flags)
+ {
+ ::sqlite3* db;
+ const int error = ::sqlite3_open_v2(file, &db, flags, NULL);
+ if (error != SQLITE_OK) {
+ if (db == NULL)
+ throw std::bad_alloc();
+ else {
+ sqlite::database error_db(utils::make_optional(fs::path(file)),
+ db, true);
+ throw sqlite::api_error::from_database(error_db,
+ "sqlite3_open_v2");
+ }
+ }
+ INV(db != NULL);
+ return db;
+ }
+
+ /// Shared code for the public close() method.
+ void
+ close(void)
+ {
+ PRE(db != NULL);
+ int error = ::sqlite3_close(db);
+ // For now, let's consider a return of SQLITE_BUSY an error. We should
+ // not be trying to close a busy database in our code. Maybe revisit
+ // this later to raise busy errors as exceptions.
+ PRE(error == SQLITE_OK);
+ db = NULL;
+ }
+};
+
+
+/// Initializes the SQLite database.
+///
+/// You must share the same database object alongside the lifetime of your
+/// SQLite session. As soon as the object is destroyed, the session is
+/// terminated.
+///
+/// \param db_filename_ The path to the database as seen at construction
+/// time, if any, or none for in-memory databases.
+/// \param db_ Raw pointer to the C SQLite 3 object.
+/// \param owned_ Whether this instance will own the pointer or not.
+sqlite::database::database(
+ const utils::optional< utils::fs::path >& db_filename_, void* db_,
+ const bool owned_) :
+ _pimpl(new impl(db_filename_, static_cast< ::sqlite3* >(db_), owned_))
+{
+}
+
+
+/// Destructor for the SQLite 3 database.
+///
+/// Closes the session unless it has already been closed by calling the
+/// close() method. It is recommended to explicitly close the session in the
+/// code.
+sqlite::database::~database(void)
+{
+}
+
+
+/// Opens a memory-based temporary SQLite database.
+///
+/// \return An in-memory database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::in_memory(void)
+{
+ return database(none, impl::safe_open(":memory:", SQLITE_OPEN_READWRITE),
+ true);
+}
+
+
+/// Opens a named on-disk SQLite database.
+///
+/// \param file The path to the database file to be opened. This does not
+/// accept the values "" and ":memory:"; use temporary() and in_memory()
+/// instead.
+/// \param open_flags The flags to be passed to the open routine.
+///
+/// \return A file-backed database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::open(const fs::path& file, int open_flags)
+{
+ PRE_MSG(!file.str().empty(), "Use database::temporary() instead");
+ PRE_MSG(file.str() != ":memory:", "Use database::in_memory() instead");
+
+ int flags = 0;
+ if (open_flags & open_readonly) {
+ flags |= SQLITE_OPEN_READONLY;
+ open_flags &= ~open_readonly;
+ }
+ if (open_flags & open_readwrite) {
+ flags |= SQLITE_OPEN_READWRITE;
+ open_flags &= ~open_readwrite;
+ }
+ if (open_flags & open_create) {
+ flags |= SQLITE_OPEN_CREATE;
+ open_flags &= ~open_create;
+ }
+ PRE(open_flags == 0);
+
+ return database(utils::make_optional(file),
+ impl::safe_open(file.c_str(), flags), true);
+}
+
+
+/// Opens an unnamed on-disk SQLite database.
+///
+/// \return A file-backed database instance.
+///
+/// \throw std::bad_alloc If there is not enough memory to open the database.
+/// \throw api_error If there is any problem opening the database.
+sqlite::database
+sqlite::database::temporary(void)
+{
+ return database(none, impl::safe_open("", SQLITE_OPEN_READWRITE), true);
+}
+
+
+/// Gets the internal sqlite3 object.
+///
+/// \return The raw SQLite 3 database. This is returned as a void pointer to
+/// prevent including the sqlite3.h header file from our public interface. The
+/// only way to call this method is by using the c_gate module, and c_gate takes
+/// care of casting this object to the appropriate type.
+void*
+sqlite::database::raw_database(void)
+{
+ return _pimpl->db;
+}
+
+
+/// Terminates the connection to the database.
+///
+/// It is recommended to call this instead of relying on the destructor to do
+/// the cleanup, but it is not a requirement to use close().
+///
+/// \pre close() has not yet been called.
+void
+sqlite::database::close(void)
+{
+ _pimpl->close();
+}
+
+
+/// Returns the path to the connected database.
+///
+/// It is OK to call this function on a live database object, even after close()
+/// has been called. The returned value is consistent at all times.
+///
+/// \return The path to the file that matches the connected database or none if
+/// the connection points to a transient database.
+const optional< fs::path >&
+sqlite::database::db_filename(void) const
+{
+ return _pimpl->db_filename;
+}
+
+
+/// Executes an arbitrary SQL string.
+///
+/// As the documentation explains, this is unsafe. The code should really be
+/// preparing statements and executing them step by step. However, it is
+/// perfectly fine to use this function for, e.g. the initial creation of
+/// tables in a database and in tests.
+///
+/// \param sql The SQL commands to be executed.
+///
+/// \throw api_error If there is any problem while processing the SQL.
+void
+sqlite::database::exec(const std::string& sql)
+{
+ const int error = ::sqlite3_exec(_pimpl->db, sql.c_str(), NULL, NULL, NULL);
+ if (error != SQLITE_OK)
+ throw api_error::from_database(*this, "sqlite3_exec");
+}
+
+
+/// Opens a new transaction.
+///
+/// \return An object representing the state of the transaction.
+///
+/// \throw api_error If there is any problem while opening the transaction.
+sqlite::transaction
+sqlite::database::begin_transaction(void)
+{
+ exec("BEGIN TRANSACTION");
+ return transaction(*this);
+}
+
+
+/// Prepares a new statement.
+///
+/// \param sql The SQL statement to prepare.
+///
+/// \return The prepared statement.
+sqlite::statement
+sqlite::database::create_statement(const std::string& sql)
+{
+ LD(F("Creating statement: %s") % sql);
+ sqlite3_stmt* stmt;
+ const int error = ::sqlite3_prepare_v2(_pimpl->db, sql.c_str(),
+ sql.length() + 1, &stmt, NULL);
+ if (error != SQLITE_OK)
+ throw api_error::from_database(*this, "sqlite3_prepare_v2");
+ return statement(*this, static_cast< void* >(stmt));
+}
+
+
+/// Returns the row identifier of the last insert.
+///
+/// \return A row identifier.
+int64_t
+sqlite::database::last_insert_rowid(void)
+{
+ return ::sqlite3_last_insert_rowid(_pimpl->db);
+}
diff --git a/utils/sqlite/database.hpp b/utils/sqlite/database.hpp
new file mode 100644
index 000000000000..ca91a6a360c6
--- /dev/null
+++ b/utils/sqlite/database.hpp
@@ -0,0 +1,111 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/database.hpp
+/// Wrapper classes and utilities for the SQLite database state.
+///
+/// This module contains thin RAII wrappers around the SQLite 3 structures
+/// representing the database, and lightweight.
+
+#if !defined(UTILS_SQLITE_DATABASE_HPP)
+#define UTILS_SQLITE_DATABASE_HPP
+
+#include "utils/sqlite/database_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstddef>
+#include <memory>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+#include "utils/sqlite/c_gate_fwd.hpp"
+#include "utils/sqlite/statement_fwd.hpp"
+#include "utils/sqlite/transaction_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Constant for the database::open flags: open in read-only mode.
+static const int open_readonly = 1 << 0;
+/// Constant for the database::open flags: open in read-write mode.
+static const int open_readwrite = 1 << 1;
+/// Constant for the database::open flags: create on open.
+static const int open_create = 1 << 2;
+
+
+/// A RAII model for the SQLite 3 database.
+///
+/// This class holds the database of the SQLite 3 interface during its existence
+/// and provides wrappers around several SQLite 3 library functions that operate
+/// on such database.
+///
+/// These wrapper functions differ from the C versions in that they use the
+/// implicit database hold by the class, they use C++ types where appropriate
+/// and they use exceptions to report errors.
+///
+/// The wrappers intend to be as lightweight as possible but, in some
+/// situations, they are pretty complex because of the workarounds needed to
+/// make the SQLite 3 more C++ friendly. We prefer a clean C++ interface over
+/// optimal efficiency, so this is OK.
+class database {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class database_c_gate;
+ database(const utils::optional< utils::fs::path >&, void*, const bool);
+ void* raw_database(void);
+
+public:
+ ~database(void);
+
+ static database in_memory(void);
+ static database open(const fs::path&, int);
+ static database temporary(void);
+ void close(void);
+
+ const utils::optional< utils::fs::path >& db_filename(void) const;
+
+ void exec(const std::string&);
+
+ transaction begin_transaction(void);
+ statement create_statement(const std::string&);
+
+ int64_t last_insert_rowid(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_DATABASE_HPP)
diff --git a/utils/sqlite/database_fwd.hpp b/utils/sqlite/database_fwd.hpp
new file mode 100644
index 000000000000..209342f159d6
--- /dev/null
+++ b/utils/sqlite/database_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/database_fwd.hpp
+/// Forward declarations for utils/sqlite/database.hpp
+
+#if !defined(UTILS_SQLITE_DATABASE_FWD_HPP)
+#define UTILS_SQLITE_DATABASE_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class database;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_DATABASE_FWD_HPP)
diff --git a/utils/sqlite/database_test.cpp b/utils/sqlite/database_test.cpp
new file mode 100644
index 000000000000..70f057b9b793
--- /dev/null
+++ b/utils/sqlite/database_test.cpp
@@ -0,0 +1,287 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/database.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/statement.ipp"
+#include "utils/sqlite/test_utils.hpp"
+#include "utils/sqlite/transaction.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::optional;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(in_memory);
+ATF_TEST_CASE_BODY(in_memory)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ verify_test_table(raw(db));
+
+ ATF_REQUIRE(!fs::exists(fs::path(":memory:")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__readonly__ok);
+ATF_TEST_CASE_BODY(open__readonly__ok)
+{
+ {
+ ::sqlite3* db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2("test.db", &db,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));
+ create_test_table(db);
+ ::sqlite3_close(db);
+ }
+ {
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readonly);
+ verify_test_table(raw(db));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__readonly__fail);
+ATF_TEST_CASE_BODY(open__readonly__fail)
+{
+ REQUIRE_API_ERROR("sqlite3_open_v2",
+ sqlite::database::open(fs::path("missing.db"), sqlite::open_readonly));
+ ATF_REQUIRE(!fs::exists(fs::path("missing.db")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open__create__ok);
+ATF_TEST_CASE_BODY(open__create__ok)
+{
+ {
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ ATF_REQUIRE(fs::exists(fs::path("test.db")));
+ create_test_table(raw(db));
+ }
+ {
+ ::sqlite3* db;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2("test.db", &db,
+ SQLITE_OPEN_READONLY, NULL));
+ verify_test_table(db);
+ ::sqlite3_close(db);
+ }
+}
+
+
+ATF_TEST_CASE(open__create__fail);
+ATF_TEST_CASE_HEAD(open__create__fail)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(open__create__fail)
+{
+ fs::mkdir(fs::path("protected"), 0555);
+ REQUIRE_API_ERROR("sqlite3_open_v2",
+ sqlite::database::open(fs::path("protected/test.db"),
+ sqlite::open_readwrite | sqlite::open_create));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(temporary);
+ATF_TEST_CASE_BODY(temporary)
+{
+ // We could validate if files go to disk by setting the temp_store_directory
+ // PRAGMA to a subdirectory of pwd, and then ensuring the subdirectory is
+ // not empty. However, there does not seem to be a way to force SQLite to
+ // unconditionally write the temporary database to disk (even with
+ // temp_store = FILE), so this scenary is hard to reproduce.
+ sqlite::database db = sqlite::database::temporary();
+ create_test_table(raw(db));
+ verify_test_table(raw(db));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(close);
+ATF_TEST_CASE_BODY(close)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.close();
+ // The destructor for the database will run now. If it does a second close,
+ // we may crash, so let's see if we don't.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(copy);
+ATF_TEST_CASE_BODY(copy)
+{
+ sqlite::database db1 = sqlite::database::in_memory();
+ {
+ sqlite::database db2 = sqlite::database::in_memory();
+ create_test_table(raw(db2));
+ db1 = db2;
+ verify_test_table(raw(db1));
+ }
+ // db2 went out of scope. If the destruction is not properly managed, the
+ // memory of db1 may have been invalidated and this would not work.
+ verify_test_table(raw(db1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__in_memory);
+ATF_TEST_CASE_BODY(db_filename__in_memory)
+{
+ const sqlite::database db = sqlite::database::in_memory();
+ ATF_REQUIRE(!db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__file);
+ATF_TEST_CASE_BODY(db_filename__file)
+{
+ const sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ ATF_REQUIRE(db.db_filename());
+ ATF_REQUIRE_EQ(fs::path("test.db"), db.db_filename().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__temporary);
+ATF_TEST_CASE_BODY(db_filename__temporary)
+{
+ const sqlite::database db = sqlite::database::temporary();
+ ATF_REQUIRE(!db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(db_filename__ok_after_close);
+ATF_TEST_CASE_BODY(db_filename__ok_after_close)
+{
+ sqlite::database db = sqlite::database::open(fs::path("test.db"),
+ sqlite::open_readwrite | sqlite::open_create);
+ const optional< fs::path > db_filename = db.db_filename();
+ ATF_REQUIRE(db_filename);
+ db.close();
+ ATF_REQUIRE_EQ(db_filename, db.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__ok);
+ATF_TEST_CASE_BODY(exec__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec(create_test_table_sql);
+ verify_test_table(raw(db));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(exec__fail);
+ATF_TEST_CASE_BODY(exec__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ REQUIRE_API_ERROR("sqlite3_exec",
+ db.exec("SELECT * FROM test"));
+ REQUIRE_API_ERROR("sqlite3_exec",
+ db.exec("CREATE TABLE test (col INTEGER PRIMARY KEY);"
+ "FOO BAR"));
+ db.exec("SELECT * FROM test");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(create_statement__ok);
+ATF_TEST_CASE_BODY(create_statement__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3");
+ // Statement testing happens in statement_test. We are only interested here
+ // in ensuring that the API call exists and runs.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(begin_transaction);
+ATF_TEST_CASE_BODY(begin_transaction)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::transaction stmt = db.begin_transaction();
+ // Transaction testing happens in transaction_test. We are only interested
+ // here in ensuring that the API call exists and runs.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(create_statement__fail);
+ATF_TEST_CASE_BODY(create_statement__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ REQUIRE_API_ERROR("sqlite3_prepare_v2",
+ db.create_statement("SELECT * FROM missing"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(last_insert_rowid);
+ATF_TEST_CASE_BODY(last_insert_rowid)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE test (a INTEGER PRIMARY KEY, b INTEGER)");
+ db.exec("INSERT INTO test VALUES (723, 5)");
+ ATF_REQUIRE_EQ(723, db.last_insert_rowid());
+ db.exec("INSERT INTO test VALUES (145, 20)");
+ ATF_REQUIRE_EQ(145, db.last_insert_rowid());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, in_memory);
+
+ ATF_ADD_TEST_CASE(tcs, open__readonly__ok);
+ ATF_ADD_TEST_CASE(tcs, open__readonly__fail);
+ ATF_ADD_TEST_CASE(tcs, open__create__ok);
+ ATF_ADD_TEST_CASE(tcs, open__create__fail);
+
+ ATF_ADD_TEST_CASE(tcs, temporary);
+
+ ATF_ADD_TEST_CASE(tcs, close);
+
+ ATF_ADD_TEST_CASE(tcs, copy);
+
+ ATF_ADD_TEST_CASE(tcs, db_filename__in_memory);
+ ATF_ADD_TEST_CASE(tcs, db_filename__file);
+ ATF_ADD_TEST_CASE(tcs, db_filename__temporary);
+ ATF_ADD_TEST_CASE(tcs, db_filename__ok_after_close);
+
+ ATF_ADD_TEST_CASE(tcs, exec__ok);
+ ATF_ADD_TEST_CASE(tcs, exec__fail);
+
+ ATF_ADD_TEST_CASE(tcs, begin_transaction);
+
+ ATF_ADD_TEST_CASE(tcs, create_statement__ok);
+ ATF_ADD_TEST_CASE(tcs, create_statement__fail);
+
+ ATF_ADD_TEST_CASE(tcs, last_insert_rowid);
+}
diff --git a/utils/sqlite/exceptions.cpp b/utils/sqlite/exceptions.cpp
new file mode 100644
index 000000000000..cc2d42cab16c
--- /dev/null
+++ b/utils/sqlite/exceptions.cpp
@@ -0,0 +1,175 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/exceptions.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <string>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::optional;
+
+
+namespace {
+
+
+/// Formats the database filename returned by sqlite for user consumption.
+///
+/// \param db_filename An optional database filename.
+///
+/// \return A string describing the filename.
+static std::string
+format_db_filename(const optional< fs::path >& db_filename)
+{
+ if (db_filename)
+ return db_filename.get().str();
+ else
+ return "in-memory or temporary";
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param message The plain-text error message.
+sqlite::error::error(const optional< fs::path >& db_filename_,
+ const std::string& message) :
+ std::runtime_error(F("%s (sqlite db: %s)") % message %
+ format_db_filename(db_filename_)),
+ _db_filename(db_filename_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::error::~error(void) throw()
+{
+}
+
+
+/// Returns the path to the database that raised this error.
+///
+/// \return A database filename as returned by database::db_filename().
+const optional< fs::path >&
+sqlite::error::db_filename(void) const
+{
+ return _db_filename;
+}
+
+
+/// Constructs a new error.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param api_function_ The name of the API function that caused the error.
+/// \param message_ The plain-text error message provided by SQLite.
+sqlite::api_error::api_error(const optional< fs::path >& db_filename_,
+ const std::string& api_function_,
+ const std::string& message_) :
+ error(db_filename_, F("%s (sqlite op: %s)") % message_ % api_function_),
+ _api_function(api_function_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::api_error::~api_error(void) throw()
+{
+}
+
+
+/// Constructs a new api_error with the message in the SQLite database.
+///
+/// \param database_ The SQLite database.
+/// \param api_function_ The name of the SQLite C API function that caused the
+/// error.
+///
+/// \return A new api_error with the retrieved message.
+sqlite::api_error
+sqlite::api_error::from_database(database& database_,
+ const std::string& api_function_)
+{
+ ::sqlite3* c_db = database_c_gate(database_).c_database();
+ return api_error(database_.db_filename(), api_function_,
+ ::sqlite3_errmsg(c_db));
+}
+
+
+/// Gets the name of the SQlite C API function that caused this error.
+///
+/// \return The name of the function.
+const std::string&
+sqlite::api_error::api_function(void) const
+{
+ return _api_function;
+}
+
+
+/// Constructs a new error.
+///
+/// \param db_filename_ Database filename as returned by database::db_filename()
+/// for error reporting purposes.
+/// \param name_ The name of the unknown column.
+sqlite::invalid_column_error::invalid_column_error(
+ const optional< fs::path >& db_filename_,
+ const std::string& name_) :
+ error(db_filename_, F("Unknown column '%s'") % name_),
+ _column_name(name_)
+{
+}
+
+
+/// Destructor for the error.
+sqlite::invalid_column_error::~invalid_column_error(void) throw()
+{
+}
+
+
+/// Gets the name of the column that could not be found.
+///
+/// \return The name of the column requested by the user.
+const std::string&
+sqlite::invalid_column_error::column_name(void) const
+{
+ return _column_name;
+}
diff --git a/utils/sqlite/exceptions.hpp b/utils/sqlite/exceptions.hpp
new file mode 100644
index 000000000000..a9450fce5c33
--- /dev/null
+++ b/utils/sqlite/exceptions.hpp
@@ -0,0 +1,94 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/exceptions.hpp
+/// Exception types raised by the sqlite module.
+
+#if !defined(UTILS_SQLITE_EXCEPTIONS_HPP)
+#define UTILS_SQLITE_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional.hpp"
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Base exception for sqlite errors.
+class error : public std::runtime_error {
+ /// Path to the database that raised this error.
+ utils::optional< utils::fs::path > _db_filename;
+
+public:
+ explicit error(const utils::optional< utils::fs::path >&,
+ const std::string&);
+ virtual ~error(void) throw();
+
+ const utils::optional< utils::fs::path >& db_filename(void) const;
+};
+
+
+/// Exception for errors raised by the SQLite 3 API library.
+class api_error : public error {
+ /// The name of the SQLite 3 C API function that caused this error.
+ std::string _api_function;
+
+public:
+ explicit api_error(const utils::optional< utils::fs::path >&,
+ const std::string&, const std::string&);
+ virtual ~api_error(void) throw();
+
+ static api_error from_database(database&, const std::string&);
+
+ const std::string& api_function(void) const;
+};
+
+
+/// The caller requested a non-existent column name.
+class invalid_column_error : public error {
+ /// The name of the invalid column.
+ std::string _column_name;
+
+public:
+ explicit invalid_column_error(const utils::optional< utils::fs::path >&,
+ const std::string&);
+ virtual ~invalid_column_error(void) throw();
+
+ const std::string& column_name(void) const;
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+
+#endif // !defined(UTILS_SQLITE_EXCEPTIONS_HPP)
diff --git a/utils/sqlite/exceptions_test.cpp b/utils/sqlite/exceptions_test.cpp
new file mode 100644
index 000000000000..d9e81038cc2f
--- /dev/null
+++ b/utils/sqlite/exceptions_test.cpp
@@ -0,0 +1,129 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/exceptions.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <cstring>
+#include <string>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+
+namespace fs = utils::fs;
+namespace sqlite = utils::sqlite;
+
+using utils::none;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error__no_filename);
+ATF_TEST_CASE_BODY(error__no_filename)
+{
+ const sqlite::database db = sqlite::database::in_memory();
+ const sqlite::error e(db.db_filename(), "Some text");
+ ATF_REQUIRE_EQ("Some text (sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ(db.db_filename(), e.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error__with_filename);
+ATF_TEST_CASE_BODY(error__with_filename)
+{
+ const sqlite::database db = sqlite::database::open(
+ fs::path("test.db"), sqlite::open_readwrite | sqlite::open_create);
+ const sqlite::error e(db.db_filename(), "Some text");
+ ATF_REQUIRE_EQ("Some text (sqlite db: test.db)", std::string(e.what()));
+ ATF_REQUIRE_EQ(db.db_filename(), e.db_filename());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(api_error__explicit);
+ATF_TEST_CASE_BODY(api_error__explicit)
+{
+ const sqlite::api_error e(none, "some_function", "Some text");
+ ATF_REQUIRE_EQ(
+ "Some text (sqlite op: some_function) "
+ "(sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("some_function", e.api_function());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(api_error__from_database);
+ATF_TEST_CASE_BODY(api_error__from_database)
+{
+ sqlite::database db = sqlite::database::open(
+ fs::path("test.db"), sqlite::open_readwrite | sqlite::open_create);
+
+ // Use the raw sqlite3 API to cause an error. Our C++ wrappers catch all
+ // errors and reraise them as exceptions, but here we want to handle the raw
+ // error directly for testing purposes.
+ sqlite::database_c_gate gate(db);
+ ::sqlite3_stmt* dummy_stmt;
+ const char* query = "ABCDE INVALID QUERY";
+ (void)::sqlite3_prepare_v2(gate.c_database(), query, std::strlen(query),
+ &dummy_stmt, NULL);
+
+ const sqlite::api_error e = sqlite::api_error::from_database(
+ db, "real_function");
+ ATF_REQUIRE_MATCH(
+ ".*ABCDE.*\\(sqlite op: real_function\\) \\(sqlite db: test.db\\)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("real_function", e.api_function());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_column_error);
+ATF_TEST_CASE_BODY(invalid_column_error)
+{
+ const sqlite::invalid_column_error e(none, "some_name");
+ ATF_REQUIRE_EQ("Unknown column 'some_name' "
+ "(sqlite db: in-memory or temporary)",
+ std::string(e.what()));
+ ATF_REQUIRE_EQ("some_name", e.column_name());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error__no_filename);
+ ATF_ADD_TEST_CASE(tcs, error__with_filename);
+
+ ATF_ADD_TEST_CASE(tcs, api_error__explicit);
+ ATF_ADD_TEST_CASE(tcs, api_error__from_database);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_column_error);
+}
diff --git a/utils/sqlite/statement.cpp b/utils/sqlite/statement.cpp
new file mode 100644
index 000000000000..0ae2af2d57ca
--- /dev/null
+++ b/utils/sqlite/statement.cpp
@@ -0,0 +1,621 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/statement.hpp"
+
+extern "C" {
+#include <sqlite3.h>
+}
+
+#include <map>
+
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+
+namespace sqlite = utils::sqlite;
+
+
+namespace {
+
+
+static sqlite::type c_type_to_cxx(const int) UTILS_PURE;
+
+
+/// Maps a SQLite 3 data type to our own representation.
+///
+/// \param original The native SQLite 3 data type.
+///
+/// \return Our internal representation for the native data type.
+static sqlite::type
+c_type_to_cxx(const int original)
+{
+ switch (original) {
+ case SQLITE_BLOB: return sqlite::type_blob;
+ case SQLITE_FLOAT: return sqlite::type_float;
+ case SQLITE_INTEGER: return sqlite::type_integer;
+ case SQLITE_NULL: return sqlite::type_null;
+ case SQLITE_TEXT: return sqlite::type_text;
+ default: UNREACHABLE_MSG("Unknown data type returned by SQLite 3");
+ }
+ UNREACHABLE;
+}
+
+
+/// Handles the return value of a sqlite3_bind_* call.
+///
+/// \param db The database the call was made on.
+/// \param api_function The name of the sqlite3_bind_* function called.
+/// \param error The error code returned by the function; can be SQLITE_OK.
+///
+/// \throw std::bad_alloc If there was no memory for the binding.
+/// \throw api_error If the binding fails for any other reason.
+static void
+handle_bind_error(sqlite::database& db, const char* api_function,
+ const int error)
+{
+ switch (error) {
+ case SQLITE_OK:
+ return;
+ case SQLITE_RANGE:
+ UNREACHABLE_MSG("Invalid index for bind argument");
+ case SQLITE_NOMEM:
+ throw std::bad_alloc();
+ default:
+ throw sqlite::api_error::from_database(db, api_function);
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Internal implementation for sqlite::statement.
+struct utils::sqlite::statement::impl : utils::noncopyable {
+ /// The database this statement belongs to.
+ sqlite::database& db;
+
+ /// The SQLite 3 internal statement.
+ ::sqlite3_stmt* stmt;
+
+ /// Cache for the column names in a statement; lazily initialized.
+ std::map< std::string, int > column_cache;
+
+ /// Constructor.
+ ///
+ /// \param db_ The database this statement belongs to. Be aware that we
+ /// keep a *reference* to the database; in other words, if the database
+ /// vanishes, this object will become invalid. (It'd be trivial to keep
+ /// a shallow copy here instead, but I feel that statements that outlive
+ /// their database represents sloppy programming.)
+ /// \param stmt_ The SQLite internal statement.
+ impl(database& db_, ::sqlite3_stmt* stmt_) :
+ db(db_),
+ stmt(stmt_)
+ {
+ }
+
+ /// Destructor.
+ ///
+ /// It is important to keep this as part of the 'impl' class instead of the
+ /// container class. The 'impl' class is destroyed exactly once (because it
+ /// is managed by a shared_ptr) and thus releasing the resources here is
+ /// OK. However, the container class is potentially released many times,
+ /// which means that we would be double-freeing the internal object and
+ /// reusing invalid data.
+ ~impl(void)
+ {
+ (void)::sqlite3_finalize(stmt);
+ }
+};
+
+
+/// Initializes a statement object.
+///
+/// This is an internal function. Use database::create_statement() to
+/// instantiate one of these objects.
+///
+/// \param db The database this statement belongs to.
+/// \param raw_stmt A void pointer representing a SQLite native statement of
+/// type sqlite3_stmt.
+sqlite::statement::statement(database& db, void* raw_stmt) :
+ _pimpl(new impl(db, static_cast< ::sqlite3_stmt* >(raw_stmt)))
+{
+}
+
+
+/// Destructor for the statement.
+///
+/// Remember that statements are reference-counted, so the statement will only
+/// cease to be valid once its last copy is destroyed.
+sqlite::statement::~statement(void)
+{
+}
+
+
+/// Executes a statement that is not supposed to return any data.
+///
+/// Use this function to execute DDL and INSERT statements; i.e. statements that
+/// only have one processing step and deliver no rows. This frees the caller
+/// from having to deal with the return value of the step() function.
+///
+/// \pre The statement to execute will not produce any rows.
+void
+sqlite::statement::step_without_results(void)
+{
+ const bool data = step();
+ INV_MSG(!data, "The statement should not have produced any rows, but it "
+ "did");
+}
+
+
+/// Performs a processing step on the statement.
+///
+/// \return True if the statement returned a row; false if the processing has
+/// finished.
+///
+/// \throw api_error If the processing of the step raises an error.
+bool
+sqlite::statement::step(void)
+{
+ const int error = ::sqlite3_step(_pimpl->stmt);
+ switch (error) {
+ case SQLITE_DONE:
+ LD("Step statement; no more rows");
+ return false;
+ case SQLITE_ROW:
+ LD("Step statement; row available for processing");
+ return true;
+ default:
+ throw api_error::from_database(_pimpl->db, "sqlite3_step");
+ }
+ UNREACHABLE;
+}
+
+
+/// Returns the number of columns in the step result.
+///
+/// \return The number of columns available for data retrieval.
+int
+sqlite::statement::column_count(void)
+{
+ return ::sqlite3_column_count(_pimpl->stmt);
+}
+
+
+/// Returns the name of a particular column in the result.
+///
+/// \param index The column to request the name of.
+///
+/// \return The name of the requested column.
+std::string
+sqlite::statement::column_name(const int index)
+{
+ const char* name = ::sqlite3_column_name(_pimpl->stmt, index);
+ if (name == NULL)
+ throw api_error::from_database(_pimpl->db, "sqlite3_column_name");
+ return name;
+}
+
+
+/// Returns the type of a particular column in the result.
+///
+/// \param index The column to request the type of.
+///
+/// \return The type of the requested column.
+sqlite::type
+sqlite::statement::column_type(const int index)
+{
+ return c_type_to_cxx(::sqlite3_column_type(_pimpl->stmt, index));
+}
+
+
+/// Finds a column by name.
+///
+/// \param name The name of the column to search for.
+///
+/// \return The column identifier.
+///
+/// \throw value_error If the name cannot be found.
+int
+sqlite::statement::column_id(const char* name)
+{
+ std::map< std::string, int >& cache = _pimpl->column_cache;
+
+ if (cache.empty()) {
+ for (int i = 0; i < column_count(); i++) {
+ const std::string aux_name = column_name(i);
+ INV(cache.find(aux_name) == cache.end());
+ cache[aux_name] = i;
+ }
+ }
+
+ const std::map< std::string, int >::const_iterator iter = cache.find(name);
+ if (iter == cache.end())
+ throw invalid_column_error(_pimpl->db.db_filename(), name);
+ else
+ return (*iter).second;
+}
+
+
+/// Returns a particular column in the result as a blob.
+///
+/// \param index The column to retrieve.
+///
+/// \return A block of memory with the blob contents. Note that the pointer
+/// returned by this call will be invalidated on the next call to any SQLite API
+/// function.
+sqlite::blob
+sqlite::statement::column_blob(const int index)
+{
+ PRE(column_type(index) == type_blob);
+ return blob(::sqlite3_column_blob(_pimpl->stmt, index),
+ ::sqlite3_column_bytes(_pimpl->stmt, index));
+}
+
+
+/// Returns a particular column in the result as a double.
+///
+/// \param index The column to retrieve.
+///
+/// \return The double value.
+double
+sqlite::statement::column_double(const int index)
+{
+ PRE(column_type(index) == type_float);
+ return ::sqlite3_column_double(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as an integer.
+///
+/// \param index The column to retrieve.
+///
+/// \return The integer value. Note that the value may not fit in an integer
+/// depending on the platform. Use column_int64 to retrieve the integer without
+/// truncation.
+int
+sqlite::statement::column_int(const int index)
+{
+ PRE(column_type(index) == type_integer);
+ return ::sqlite3_column_int(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as a 64-bit integer.
+///
+/// \param index The column to retrieve.
+///
+/// \return The integer value.
+int64_t
+sqlite::statement::column_int64(const int index)
+{
+ PRE(column_type(index) == type_integer);
+ return ::sqlite3_column_int64(_pimpl->stmt, index);
+}
+
+
+/// Returns a particular column in the result as a double.
+///
+/// \param index The column to retrieve.
+///
+/// \return A C string with the contents. Note that the pointer returned by
+/// this call will be invalidated on the next call to any SQLite API function.
+/// If you want to be extra safe, store the result in a std::string to not worry
+/// about this.
+std::string
+sqlite::statement::column_text(const int index)
+{
+ PRE(column_type(index) == type_text);
+ return reinterpret_cast< const char* >(::sqlite3_column_text(
+ _pimpl->stmt, index));
+}
+
+
+/// Returns the number of bytes stored in the column.
+///
+/// \pre This is only valid for columns of type blob and text.
+///
+/// \param index The column to retrieve the size of.
+///
+/// \return The number of bytes in the column. Remember that strings are stored
+/// in their UTF-8 representation; this call returns the number of *bytes*, not
+/// characters.
+int
+sqlite::statement::column_bytes(const int index)
+{
+ PRE(column_type(index) == type_blob || column_type(index) == type_text);
+ return ::sqlite3_column_bytes(_pimpl->stmt, index);
+}
+
+
+/// Type-checked version of column_blob.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_blob if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+sqlite::blob
+sqlite::statement::safe_column_blob(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_blob)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a blob") % name);
+ return column_blob(column);
+}
+
+
+/// Type-checked version of column_double.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_double if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+double
+sqlite::statement::safe_column_double(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_float)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a float") % name);
+ return column_double(column);
+}
+
+
+/// Type-checked version of column_int.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_int if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+int
+sqlite::statement::safe_column_int(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_integer)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not an integer") % name);
+ return column_int(column);
+}
+
+
+/// Type-checked version of column_int64.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_int64 if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+int64_t
+sqlite::statement::safe_column_int64(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_integer)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not an integer") % name);
+ return column_int64(column);
+}
+
+
+/// Type-checked version of column_text.
+///
+/// \param name The name of the column to retrieve.
+///
+/// \return The same as column_text if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve is invalid.
+/// \throw invalid_column_error If name is invalid.
+std::string
+sqlite::statement::safe_column_text(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_text)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a string") % name);
+ return column_text(column);
+}
+
+
+/// Type-checked version of column_bytes.
+///
+/// \param name The name of the column to retrieve the size of.
+///
+/// \return The same as column_bytes if the value can be retrieved.
+///
+/// \throw error If the type of the cell to retrieve the size of is invalid.
+/// \throw invalid_column_error If name is invalid.
+int
+sqlite::statement::safe_column_bytes(const char* name)
+{
+ const int column = column_id(name);
+ if (column_type(column) != sqlite::type_blob &&
+ column_type(column) != sqlite::type_text)
+ throw sqlite::error(_pimpl->db.db_filename(),
+ F("Column '%s' is not a blob or a string") % name);
+ return column_bytes(column);
+}
+
+
+/// Resets a statement to allow further processing.
+void
+sqlite::statement::reset(void)
+{
+ (void)::sqlite3_reset(_pimpl->stmt);
+}
+
+
+/// Binds a blob to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param b Description of the blob, which must remain valid during the
+/// execution of the statement.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const blob& b)
+{
+ const int error = ::sqlite3_bind_blob(_pimpl->stmt, index, b.memory, b.size,
+ SQLITE_STATIC);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_blob", error);
+}
+
+
+/// Binds a double value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The double value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const double value)
+{
+ const int error = ::sqlite3_bind_double(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_double", error);
+}
+
+
+/// Binds an integer value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The integer value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const int value)
+{
+ const int error = ::sqlite3_bind_int(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_int", error);
+}
+
+
+/// Binds a 64-bit integer value to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param value The 64-bin integer value to bind.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const int64_t value)
+{
+ const int error = ::sqlite3_bind_int64(_pimpl->stmt, index, value);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_int64", error);
+}
+
+
+/// Binds a NULL value to a prepared statement.
+///
+/// \param index The index of the binding.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const null& /* null */)
+{
+ const int error = ::sqlite3_bind_null(_pimpl->stmt, index);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_null", error);
+}
+
+
+/// Binds a text string to a prepared statement.
+///
+/// \param index The index of the binding.
+/// \param text The string to bind. SQLite generates an internal copy of this
+/// string, so the original string object does not have to remain live. We
+/// do this because handling the lifetime of std::string objects is very
+/// hard (think about implicit conversions), so it is very easy to shoot
+/// ourselves in the foot if we don't do this.
+///
+/// \throw api_error If the binding fails.
+void
+sqlite::statement::bind(const int index, const std::string& text)
+{
+ const int error = ::sqlite3_bind_text(_pimpl->stmt, index, text.c_str(),
+ text.length(), SQLITE_TRANSIENT);
+ handle_bind_error(_pimpl->db, "sqlite3_bind_text", error);
+}
+
+
+/// Returns the index of the highest parameter.
+///
+/// \return A parameter index.
+int
+sqlite::statement::bind_parameter_count(void)
+{
+ return ::sqlite3_bind_parameter_count(_pimpl->stmt);
+}
+
+
+/// Returns the index of a named parameter.
+///
+/// \param name The name of the parameter to be queried; must exist.
+///
+/// \return A parameter index.
+int
+sqlite::statement::bind_parameter_index(const std::string& name)
+{
+ const int index = ::sqlite3_bind_parameter_index(_pimpl->stmt,
+ name.c_str());
+ PRE_MSG(index > 0, "Parameter name not in statement");
+ return index;
+}
+
+
+/// Returns the name of a parameter by index.
+///
+/// \param index The index to query; must be valid.
+///
+/// \return The name of the parameter.
+std::string
+sqlite::statement::bind_parameter_name(const int index)
+{
+ const char* name = ::sqlite3_bind_parameter_name(_pimpl->stmt, index);
+ PRE_MSG(name != NULL, "Index value out of range or nameless parameter");
+ return std::string(name);
+}
+
+
+/// Clears any bindings and releases their memory.
+void
+sqlite::statement::clear_bindings(void)
+{
+ const int error = ::sqlite3_clear_bindings(_pimpl->stmt);
+ PRE_MSG(error == SQLITE_OK, "SQLite3 contract has changed; it should "
+ "only return SQLITE_OK");
+}
diff --git a/utils/sqlite/statement.hpp b/utils/sqlite/statement.hpp
new file mode 100644
index 000000000000..bcd1831e4841
--- /dev/null
+++ b/utils/sqlite/statement.hpp
@@ -0,0 +1,137 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/statement.hpp
+/// Wrapper classes and utilities for SQLite statement processing.
+///
+/// This module contains thin RAII wrappers around the SQLite 3 structures
+/// representing statements.
+
+#if !defined(UTILS_SQLITE_STATEMENT_HPP)
+#define UTILS_SQLITE_STATEMENT_HPP
+
+#include "utils/sqlite/statement_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <memory>
+#include <string>
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// Representation of a BLOB.
+class blob {
+public:
+ /// Memory representing the contents of the blob, or NULL if empty.
+ ///
+ /// This memory must remain valid throughout the life of this object, as we
+ /// do not grab ownership of the memory.
+ const void* memory;
+
+ /// Number of bytes in memory.
+ int size;
+
+ /// Constructs a new blob.
+ ///
+ /// \param memory_ Pointer to the contents of the blob.
+ /// \param size_ The size of memory_.
+ blob(const void* memory_, const int size_) :
+ memory(memory_), size(size_)
+ {
+ }
+};
+
+
+/// Representation of a SQL NULL value.
+class null {
+};
+
+
+/// A RAII model for an SQLite 3 statement.
+class statement {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ statement(database&, void*);
+ friend class database;
+
+public:
+ ~statement(void);
+
+ bool step(void);
+ void step_without_results(void);
+
+ int column_count(void);
+ std::string column_name(const int);
+ type column_type(const int);
+ int column_id(const char*);
+
+ blob column_blob(const int);
+ double column_double(const int);
+ int column_int(const int);
+ int64_t column_int64(const int);
+ std::string column_text(const int);
+ int column_bytes(const int);
+
+ blob safe_column_blob(const char*);
+ double safe_column_double(const char*);
+ int safe_column_int(const char*);
+ int64_t safe_column_int64(const char*);
+ std::string safe_column_text(const char*);
+ int safe_column_bytes(const char*);
+
+ void reset(void);
+
+ void bind(const int, const blob&);
+ void bind(const int, const double);
+ void bind(const int, const int);
+ void bind(const int, const int64_t);
+ void bind(const int, const null&);
+ void bind(const int, const std::string&);
+ template< class T > void bind(const char*, const T&);
+
+ int bind_parameter_count(void);
+ int bind_parameter_index(const std::string&);
+ std::string bind_parameter_name(const int);
+
+ void clear_bindings(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_HPP)
diff --git a/utils/sqlite/statement.ipp b/utils/sqlite/statement.ipp
new file mode 100644
index 000000000000..3f219016a2a9
--- /dev/null
+++ b/utils/sqlite/statement.ipp
@@ -0,0 +1,52 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_SQLITE_STATEMENT_IPP)
+#define UTILS_SQLITE_STATEMENT_IPP
+
+#include "utils/sqlite/statement.hpp"
+
+
+/// Binds a value to a parameter of a prepared statement.
+///
+/// \param parameter The name of the parameter; must exist. This is a raw C
+/// string instead of a std::string because statement parameter names are
+/// known at compilation time and the program should really not be
+/// constructing them dynamically.
+/// \param value The value to bind to the parameter.
+///
+/// \throw api_error If the binding fails.
+template< class T >
+void
+utils::sqlite::statement::bind(const char* parameter, const T& value)
+{
+ bind(bind_parameter_index(parameter), value);
+}
+
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_IPP)
diff --git a/utils/sqlite/statement_fwd.hpp b/utils/sqlite/statement_fwd.hpp
new file mode 100644
index 000000000000..26634c965018
--- /dev/null
+++ b/utils/sqlite/statement_fwd.hpp
@@ -0,0 +1,57 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/statement_fwd.hpp
+/// Forward declarations for utils/sqlite/statement.hpp
+
+#if !defined(UTILS_SQLITE_STATEMENT_FWD_HPP)
+#define UTILS_SQLITE_STATEMENT_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+/// Representation of the SQLite data types.
+enum type {
+ type_blob,
+ type_float,
+ type_integer,
+ type_null,
+ type_text,
+};
+
+
+class blob;
+class null;
+class statement;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_STATEMENT_FWD_HPP)
diff --git a/utils/sqlite/statement_test.cpp b/utils/sqlite/statement_test.cpp
new file mode 100644
index 000000000000..40bc92cb5c0e
--- /dev/null
+++ b/utils/sqlite/statement_test.cpp
@@ -0,0 +1,784 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/statement.ipp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstring>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/test_utils.hpp"
+
+namespace sqlite = utils::sqlite;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__ok);
+ATF_TEST_CASE_BODY(step__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE_THROW(sqlite::error, db.exec("SELECT * FROM foo"));
+ ATF_REQUIRE(!stmt.step());
+ db.exec("SELECT * FROM foo");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__many);
+ATF_TEST_CASE_BODY(step__many)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ create_test_table(raw(db));
+ sqlite::statement stmt = db.create_statement(
+ "SELECT prime FROM test ORDER BY prime");
+ for (int i = 0; i < 5; i++)
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step__fail);
+ATF_TEST_CASE_BODY(step__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE(!stmt.step());
+ REQUIRE_API_ERROR("sqlite3_step", stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step_without_results__ok);
+ATF_TEST_CASE_BODY(step_without_results__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement(
+ "CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ ATF_REQUIRE_THROW(sqlite::error, db.exec("SELECT * FROM foo"));
+ stmt.step_without_results();
+ db.exec("SELECT * FROM foo");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(step_without_results__fail);
+ATF_TEST_CASE_BODY(step_without_results__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO foo VALUES (3)");
+ sqlite::statement stmt = db.create_statement(
+ "INSERT INTO foo VALUES (3)");
+ REQUIRE_API_ERROR("sqlite3_step", stmt.step_without_results());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_count);
+ATF_TEST_CASE_BODY(column_count)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (5, 3, 'foo');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(3, stmt.column_count());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_name__ok);
+ATF_TEST_CASE_BODY(column_name__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (first INTEGER PRIMARY KEY, second TEXT);"
+ "INSERT INTO foo VALUES (5, 'foo');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("first", stmt.column_name(0));
+ ATF_REQUIRE_EQ("second", stmt.column_name(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_name__fail);
+ATF_TEST_CASE_BODY(column_name__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (first INTEGER PRIMARY KEY);"
+ "INSERT INTO foo VALUES (5);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("first", stmt.column_name(0));
+ REQUIRE_API_ERROR("sqlite3_column_name", stmt.column_name(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_type__ok);
+ATF_TEST_CASE_BODY(column_type__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a_blob BLOB,"
+ " a_float FLOAT,"
+ " an_integer INTEGER,"
+ " a_null BLOB,"
+ " a_text TEXT);"
+ "INSERT INTO foo VALUES (x'0102', 0.3, 5, NULL, 'foo bar');"
+ "INSERT INTO foo VALUES (NULL, NULL, NULL, NULL, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_blob == stmt.column_type(0));
+ ATF_REQUIRE(sqlite::type_float == stmt.column_type(1));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(2));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(3));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(4));
+ ATF_REQUIRE(stmt.step());
+ for (int i = 0; i < stmt.column_count(); i++)
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(i));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_type__out_of_range);
+ATF_TEST_CASE_BODY(column_type__out_of_range)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER PRIMARY KEY);"
+ "INSERT INTO foo VALUES (1);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(512));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_id__ok);
+ATF_TEST_CASE_BODY(column_id__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (bar INTEGER PRIMARY KEY, "
+ " baz INTEGER);"
+ "INSERT INTO foo VALUES (1, 2);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ ATF_REQUIRE_EQ(1, stmt.column_id("baz"));
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ ATF_REQUIRE_EQ(1, stmt.column_id("baz"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_id__missing);
+ATF_TEST_CASE_BODY(column_id__missing)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (bar INTEGER PRIMARY KEY, "
+ " baz INTEGER);"
+ "INSERT INTO foo VALUES (1, 2);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0, stmt.column_id("bar"));
+ try {
+ stmt.column_id("bazo");
+ fail("invalid_column_error not raised");
+ } catch (const sqlite::invalid_column_error& e) {
+ ATF_REQUIRE_EQ("bazo", e.column_name());
+ }
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_blob);
+ATF_TEST_CASE_BODY(column_blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b BLOB, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, x'cafe', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ const sqlite::blob blob = stmt.column_blob(1);
+ ATF_REQUIRE_EQ(0xca, static_cast< const uint8_t* >(blob.memory)[0]);
+ ATF_REQUIRE_EQ(0xfe, static_cast< const uint8_t* >(blob.memory)[1]);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_double);
+ATF_TEST_CASE_BODY(column_double)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b DOUBLE, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 0.5, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0.5, stmt.column_double(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int__ok);
+ATF_TEST_CASE_BODY(column_int__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 987, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(987, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int__overflow);
+ATF_TEST_CASE_BODY(column_int__overflow)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(123, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_int64);
+ATF_TEST_CASE_BODY(column_int64)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4294967419LL, stmt.column_int64(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_text);
+ATF_TEST_CASE_BODY(column_text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b TEXT, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 'foo bar', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("foo bar", stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_bytes__blob);
+ATF_TEST_CASE_BODY(column_bytes__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a BLOB);"
+ "INSERT INTO foo VALUES (x'12345678');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4, stmt.column_bytes(0));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(column_bytes__text);
+ATF_TEST_CASE_BODY(column_bytes__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(7, stmt.column_bytes(0));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_blob__ok);
+ATF_TEST_CASE_BODY(safe_column_blob__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b BLOB, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, x'cafe', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ const sqlite::blob blob = stmt.safe_column_blob("b");
+ ATF_REQUIRE_EQ(0xca, static_cast< const uint8_t* >(blob.memory)[0]);
+ ATF_REQUIRE_EQ(0xfe, static_cast< const uint8_t* >(blob.memory)[1]);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_blob__fail);
+ATF_TEST_CASE_BODY(safe_column_blob__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (123);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_blob("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a blob",
+ stmt.safe_column_blob("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_double__ok);
+ATF_TEST_CASE_BODY(safe_column_double__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b DOUBLE, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 0.5, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(0.5, stmt.safe_column_double("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_double__fail);
+ATF_TEST_CASE_BODY(safe_column_double__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_double("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a float",
+ stmt.safe_column_double("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int__ok);
+ATF_TEST_CASE_BODY(safe_column_int__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 987, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(987, stmt.safe_column_int("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int__fail);
+ATF_TEST_CASE_BODY(safe_column_int__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('def');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_int("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not an integer",
+ stmt.safe_column_int("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int64__ok);
+ATF_TEST_CASE_BODY(safe_column_int64__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT, b INTEGER, c TEXT);"
+ "INSERT INTO foo VALUES (NULL, 4294967419, NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4294967419LL, stmt.safe_column_int64("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_int64__fail);
+ATF_TEST_CASE_BODY(safe_column_int64__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('abc');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_int64("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not an integer",
+ stmt.safe_column_int64("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_text__ok);
+ATF_TEST_CASE_BODY(safe_column_text__ok)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER, b TEXT, c INTEGER);"
+ "INSERT INTO foo VALUES (NULL, 'foo bar', NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ("foo bar", stmt.safe_column_text("b"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_text__fail);
+ATF_TEST_CASE_BODY(safe_column_text__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a INTEGER);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_text("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a string",
+ stmt.safe_column_text("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__ok__blob);
+ATF_TEST_CASE_BODY(safe_column_bytes__ok__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a BLOB);"
+ "INSERT INTO foo VALUES (x'12345678');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(4, stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__ok__text);
+ATF_TEST_CASE_BODY(safe_column_bytes__ok__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_EQ(7, stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(safe_column_bytes__fail);
+ATF_TEST_CASE_BODY(safe_column_bytes__fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES (NULL);");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE_THROW(sqlite::invalid_column_error,
+ stmt.safe_column_bytes("b"));
+ ATF_REQUIRE_THROW_RE(sqlite::error, "not a blob or a string",
+ stmt.safe_column_bytes("a"));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset);
+ATF_TEST_CASE_BODY(reset)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE foo (a TEXT);"
+ "INSERT INTO foo VALUES ('foo bar');");
+ sqlite::statement stmt = db.create_statement("SELECT * FROM foo");
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+ stmt.reset();
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__blob);
+ATF_TEST_CASE_BODY(bind__blob)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ const unsigned char blob[] = {0xca, 0xfe};
+ stmt.bind(1, sqlite::blob(static_cast< const void* >(blob), 2));
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_blob == stmt.column_type(1));
+ const unsigned char* ret_blob =
+ static_cast< const unsigned char* >(stmt.column_blob(1).memory);
+ ATF_REQUIRE(std::memcmp(blob, ret_blob, 2) == 0);
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__double);
+ATF_TEST_CASE_BODY(bind__double)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 0.5);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_float == stmt.column_type(1));
+ ATF_REQUIRE_EQ(0.5, stmt.column_double(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__int);
+ATF_TEST_CASE_BODY(bind__int)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 123);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(123, stmt.column_int(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__int64);
+ATF_TEST_CASE_BODY(bind__int64)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, static_cast< int64_t >(4294967419LL));
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(4294967419LL, stmt.column_int64(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__null);
+ATF_TEST_CASE_BODY(bind__null)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, sqlite::null());
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__text);
+ATF_TEST_CASE_BODY(bind__text)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ const std::string str = "Hello";
+ stmt.bind(1, str);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(str, stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__text__transient);
+ATF_TEST_CASE_BODY(bind__text__transient)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo");
+
+ {
+ const std::string str = "Hello";
+ stmt.bind(":foo", str);
+ }
+
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(std::string("Hello"), stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind__by_name);
+ATF_TEST_CASE_BODY(bind__by_name)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo");
+
+ const std::string str = "Hello";
+ stmt.bind(":foo", str);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_text == stmt.column_type(1));
+ ATF_REQUIRE_EQ(str, stmt.column_text(1));
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_count);
+ATF_TEST_CASE_BODY(bind_parameter_count)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?, ?");
+ ATF_REQUIRE_EQ(2, stmt.bind_parameter_count());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_index);
+ATF_TEST_CASE_BODY(bind_parameter_index)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo, ?, :bar");
+ ATF_REQUIRE_EQ(1, stmt.bind_parameter_index(":foo"));
+ ATF_REQUIRE_EQ(3, stmt.bind_parameter_index(":bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bind_parameter_name);
+ATF_TEST_CASE_BODY(bind_parameter_name)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, :foo, ?, :bar");
+ ATF_REQUIRE_EQ(":foo", stmt.bind_parameter_name(1));
+ ATF_REQUIRE_EQ(":bar", stmt.bind_parameter_name(3));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(clear_bindings);
+ATF_TEST_CASE_BODY(clear_bindings)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ sqlite::statement stmt = db.create_statement("SELECT 3, ?");
+
+ stmt.bind(1, 5);
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(1));
+ ATF_REQUIRE_EQ(5, stmt.column_int(1));
+ stmt.clear_bindings();
+ stmt.reset();
+
+ ATF_REQUIRE(stmt.step());
+ ATF_REQUIRE(sqlite::type_integer == stmt.column_type(0));
+ ATF_REQUIRE_EQ(3, stmt.column_int(0));
+ ATF_REQUIRE(sqlite::type_null == stmt.column_type(1));
+
+ ATF_REQUIRE(!stmt.step());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, step__ok);
+ ATF_ADD_TEST_CASE(tcs, step__many);
+ ATF_ADD_TEST_CASE(tcs, step__fail);
+
+ ATF_ADD_TEST_CASE(tcs, step_without_results__ok);
+ ATF_ADD_TEST_CASE(tcs, step_without_results__fail);
+
+ ATF_ADD_TEST_CASE(tcs, column_count);
+
+ ATF_ADD_TEST_CASE(tcs, column_name__ok);
+ ATF_ADD_TEST_CASE(tcs, column_name__fail);
+
+ ATF_ADD_TEST_CASE(tcs, column_type__ok);
+ ATF_ADD_TEST_CASE(tcs, column_type__out_of_range);
+
+ ATF_ADD_TEST_CASE(tcs, column_id__ok);
+ ATF_ADD_TEST_CASE(tcs, column_id__missing);
+
+ ATF_ADD_TEST_CASE(tcs, column_blob);
+ ATF_ADD_TEST_CASE(tcs, column_double);
+ ATF_ADD_TEST_CASE(tcs, column_int__ok);
+ ATF_ADD_TEST_CASE(tcs, column_int__overflow);
+ ATF_ADD_TEST_CASE(tcs, column_int64);
+ ATF_ADD_TEST_CASE(tcs, column_text);
+
+ ATF_ADD_TEST_CASE(tcs, column_bytes__blob);
+ ATF_ADD_TEST_CASE(tcs, column_bytes__text);
+
+ ATF_ADD_TEST_CASE(tcs, safe_column_blob__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_blob__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_double__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_double__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int64__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_int64__fail);
+ ATF_ADD_TEST_CASE(tcs, safe_column_text__ok);
+ ATF_ADD_TEST_CASE(tcs, safe_column_text__fail);
+
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__ok__blob);
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__ok__text);
+ ATF_ADD_TEST_CASE(tcs, safe_column_bytes__fail);
+
+ ATF_ADD_TEST_CASE(tcs, reset);
+
+ ATF_ADD_TEST_CASE(tcs, bind__blob);
+ ATF_ADD_TEST_CASE(tcs, bind__double);
+ ATF_ADD_TEST_CASE(tcs, bind__int64);
+ ATF_ADD_TEST_CASE(tcs, bind__int);
+ ATF_ADD_TEST_CASE(tcs, bind__null);
+ ATF_ADD_TEST_CASE(tcs, bind__text);
+ ATF_ADD_TEST_CASE(tcs, bind__text__transient);
+ ATF_ADD_TEST_CASE(tcs, bind__by_name);
+
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_count);
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_index);
+ ATF_ADD_TEST_CASE(tcs, bind_parameter_name);
+
+ ATF_ADD_TEST_CASE(tcs, clear_bindings);
+}
diff --git a/utils/sqlite/test_utils.hpp b/utils/sqlite/test_utils.hpp
new file mode 100644
index 000000000000..bf35d209a164
--- /dev/null
+++ b/utils/sqlite/test_utils.hpp
@@ -0,0 +1,151 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/test_utils.hpp
+/// Utilities for tests of the sqlite modules.
+///
+/// This file is intended to be included once, and only once, for every test
+/// program that needs it. All the code is herein contained to simplify the
+/// dependency chain in the build rules.
+
+#if !defined(UTILS_SQLITE_TEST_UTILS_HPP)
+# define UTILS_SQLITE_TEST_UTILS_HPP
+#else
+# error "test_utils.hpp can only be included once"
+#endif
+
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/sqlite/c_gate.hpp"
+#include "utils/sqlite/exceptions.hpp"
+
+
+namespace {
+
+
+/// Checks that a given expression raises a particular sqlite::api_error.
+///
+/// We cannot make any assumptions regarding the error text provided by SQLite,
+/// so we resort to checking only which API function raised the error (because
+/// our code is the one hardcoding these strings).
+///
+/// \param exp_api_function The name of the SQLite 3 C API function that causes
+/// the error.
+/// \param statement The statement to execute.
+#define REQUIRE_API_ERROR(exp_api_function, statement) \
+ do { \
+ try { \
+ statement; \
+ ATF_FAIL("api_error not raised by " #statement); \
+ } catch (const utils::sqlite::api_error& api_error) { \
+ ATF_REQUIRE_EQ(exp_api_function, api_error.api_function()); \
+ } \
+ } while (0)
+
+
+/// Gets the pointer to the internal sqlite3 of a database object.
+///
+/// This is pure syntactic sugar to simplify typing in the test cases.
+///
+/// \param db The SQLite database.
+///
+/// \return The internal sqlite3 of the input database.
+static inline ::sqlite3*
+raw(utils::sqlite::database& db)
+{
+ return utils::sqlite::database_c_gate(db).c_database();
+}
+
+
+/// SQL commands to create a test table.
+///
+/// See create_test_table() for more details.
+static const char* create_test_table_sql =
+ "CREATE TABLE test (prime INTEGER PRIMARY KEY);"
+ "INSERT INTO test (prime) VALUES (1);\n"
+ "INSERT INTO test (prime) VALUES (2);\n"
+ "INSERT INTO test (prime) VALUES (7);\n"
+ "INSERT INTO test (prime) VALUES (5);\n"
+ "INSERT INTO test (prime) VALUES (3);\n";
+
+
+static void create_test_table(::sqlite3*) UTILS_UNUSED;
+
+
+/// Creates a 'test' table in a database.
+///
+/// The created 'test' table is populated with a few rows. If there are any
+/// problems creating the database, this fails the test case.
+///
+/// Use the verify_test_table() function on the same database to verify that
+/// the table is present and contains the expected data.
+///
+/// \param db The database in which to create the test table.
+static void
+create_test_table(::sqlite3* db)
+{
+ std::cout << "Creating 'test' table in the database\n";
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_exec(db, create_test_table_sql,
+ NULL, NULL, NULL));
+}
+
+
+static void verify_test_table(::sqlite3*) UTILS_UNUSED;
+
+
+/// Verifies that the specified database contains the 'test' table.
+///
+/// This function ensures that the provided database contains the 'test' table
+/// created by the create_test_table() function on the same database. If it
+/// doesn't, this fails the caller test case.
+///
+/// \param db The database to validate.
+static void
+verify_test_table(::sqlite3* db)
+{
+ std::cout << "Verifying that the 'test' table is in the database\n";
+ char **result;
+ int rows, columns;
+ ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_get_table(db,
+ "SELECT * FROM test ORDER BY prime", &result, &rows, &columns, NULL));
+ ATF_REQUIRE_EQ(5, rows);
+ ATF_REQUIRE_EQ(1, columns);
+ ATF_REQUIRE_EQ("prime", std::string(result[0]));
+ ATF_REQUIRE_EQ("1", std::string(result[1]));
+ ATF_REQUIRE_EQ("2", std::string(result[2]));
+ ATF_REQUIRE_EQ("3", std::string(result[3]));
+ ATF_REQUIRE_EQ("5", std::string(result[4]));
+ ATF_REQUIRE_EQ("7", std::string(result[5]));
+ ::sqlite3_free_table(result);
+}
+
+
+} // anonymous namespace
diff --git a/utils/sqlite/transaction.cpp b/utils/sqlite/transaction.cpp
new file mode 100644
index 000000000000..e0235fef9c57
--- /dev/null
+++ b/utils/sqlite/transaction.cpp
@@ -0,0 +1,142 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/transaction.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+
+namespace sqlite = utils::sqlite;
+
+
+/// Internal implementation for the transaction.
+struct utils::sqlite::transaction::impl : utils::noncopyable {
+ /// The database this transaction belongs to.
+ database& db;
+
+ /// Possible statuses of a transaction.
+ enum statuses {
+ open_status,
+ committed_status,
+ rolled_back_status,
+ };
+
+ /// The current status of the transaction.
+ statuses status;
+
+ /// Constructs a new transaction.
+ ///
+ /// \param db_ The database this transaction belongs to.
+ /// \param status_ The status of the new transaction.
+ impl(database& db_, const statuses status_) :
+ db(db_),
+ status(status_)
+ {
+ }
+
+ /// Destroys the transaction.
+ ///
+ /// This rolls back the transaction if it is open.
+ ~impl(void)
+ {
+ if (status == impl::open_status) {
+ try {
+ rollback();
+ } catch (const sqlite::error& e) {
+ LW(F("Error while rolling back a transaction: %s") % e.what());
+ }
+ }
+ }
+
+ /// Commits the transaction.
+ ///
+ /// \throw api_error If there is any problem while committing the
+ /// transaction.
+ void
+ commit(void)
+ {
+ PRE(status == impl::open_status);
+ db.exec("COMMIT");
+ status = impl::committed_status;
+ }
+
+ /// Rolls the transaction back.
+ ///
+ /// \throw api_error If there is any problem while rolling the
+ /// transaction back.
+ void
+ rollback(void)
+ {
+ PRE(status == impl::open_status);
+ db.exec("ROLLBACK");
+ status = impl::rolled_back_status;
+ }
+};
+
+
+/// Initializes a transaction object.
+///
+/// This is an internal function. Use database::begin_transaction() to
+/// instantiate one of these objects.
+///
+/// \param db The database this transaction belongs to.
+sqlite::transaction::transaction(database& db) :
+ _pimpl(new impl(db, impl::open_status))
+{
+}
+
+
+/// Destructor for the transaction.
+sqlite::transaction::~transaction(void)
+{
+}
+
+
+/// Commits the transaction.
+///
+/// \throw api_error If there is any problem while committing the transaction.
+void
+sqlite::transaction::commit(void)
+{
+ _pimpl->commit();
+}
+
+
+/// Rolls the transaction back.
+///
+/// \throw api_error If there is any problem while rolling the transaction back.
+void
+sqlite::transaction::rollback(void)
+{
+ _pimpl->rollback();
+}
diff --git a/utils/sqlite/transaction.hpp b/utils/sqlite/transaction.hpp
new file mode 100644
index 000000000000..71f3b0c93f4a
--- /dev/null
+++ b/utils/sqlite/transaction.hpp
@@ -0,0 +1,69 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/transaction.hpp
+/// A RAII model for SQLite transactions.
+
+#if !defined(UTILS_SQLITE_TRANSACTION_HPP)
+#define UTILS_SQLITE_TRANSACTION_HPP
+
+#include "utils/sqlite/transaction_fwd.hpp"
+
+#include <memory>
+
+#include "utils/sqlite/database_fwd.hpp"
+
+namespace utils {
+namespace sqlite {
+
+
+/// A RAII model for an SQLite 3 statement.
+///
+/// A transaction is automatically rolled back when it goes out of scope unless
+/// it has been explicitly committed.
+class transaction {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ explicit transaction(database&);
+ friend class database;
+
+public:
+ ~transaction(void);
+
+ void commit(void);
+ void rollback(void);
+};
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_TRANSACTION_HPP)
diff --git a/utils/sqlite/transaction_fwd.hpp b/utils/sqlite/transaction_fwd.hpp
new file mode 100644
index 000000000000..7773d8380458
--- /dev/null
+++ b/utils/sqlite/transaction_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/sqlite/transaction_fwd.hpp
+/// Forward declarations for utils/sqlite/transaction.hpp
+
+#if !defined(UTILS_SQLITE_TRANSACTION_FWD_HPP)
+#define UTILS_SQLITE_TRANSACTION_FWD_HPP
+
+namespace utils {
+namespace sqlite {
+
+
+class transaction;
+
+
+} // namespace sqlite
+} // namespace utils
+
+#endif // !defined(UTILS_SQLITE_TRANSACTION_FWD_HPP)
diff --git a/utils/sqlite/transaction_test.cpp b/utils/sqlite/transaction_test.cpp
new file mode 100644
index 000000000000..d53e6fba4378
--- /dev/null
+++ b/utils/sqlite/transaction_test.cpp
@@ -0,0 +1,135 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/sqlite/transaction.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/sqlite/database.hpp"
+#include "utils/sqlite/exceptions.hpp"
+#include "utils/sqlite/statement.ipp"
+
+namespace sqlite = utils::sqlite;
+
+
+namespace {
+
+
+/// Ensures that a table has a single specific value in a column.
+///
+/// \param db The SQLite database.
+/// \param table_name The table to be checked.
+/// \param column_name The column to be checked.
+/// \param exp_value The value expected to be found in the column.
+///
+/// \return True if the column contains a single value and it matches exp_value;
+/// false if not. If the query fails, the calling test is marked as bad.
+static bool
+check_in_table(sqlite::database& db, const char* table_name,
+ const char* column_name, int exp_value)
+{
+ sqlite::statement stmt = db.create_statement(
+ F("SELECT * FROM %s WHERE %s == %s") % table_name % column_name %
+ exp_value);
+ if (!stmt.step())
+ return false;
+ if (stmt.step())
+ ATF_FAIL("More than one value found in table");
+ return true;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(automatic_rollback);
+ATF_TEST_CASE_BODY(automatic_rollback)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ }
+ ATF_REQUIRE( check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(!check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(explicit_commit);
+ATF_TEST_CASE_BODY(explicit_commit)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ tx.commit();
+ }
+ ATF_REQUIRE(check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(explicit_rollback);
+ATF_TEST_CASE_BODY(explicit_rollback)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ db.exec("CREATE TABLE t (col INTEGER PRIMARY KEY)");
+ db.exec("INSERT INTO t VALUES (3)");
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ db.exec("INSERT INTO t VALUES (5)");
+ tx.rollback();
+ }
+ ATF_REQUIRE( check_in_table(db, "t", "col", 3));
+ ATF_REQUIRE(!check_in_table(db, "t", "col", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(nested_fail);
+ATF_TEST_CASE_BODY(nested_fail)
+{
+ sqlite::database db = sqlite::database::in_memory();
+ {
+ sqlite::transaction tx = db.begin_transaction();
+ ATF_REQUIRE_THROW(sqlite::error, db.begin_transaction());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, automatic_rollback);
+ ATF_ADD_TEST_CASE(tcs, explicit_commit);
+ ATF_ADD_TEST_CASE(tcs, explicit_rollback);
+ ATF_ADD_TEST_CASE(tcs, nested_fail);
+}
diff --git a/utils/stacktrace.cpp b/utils/stacktrace.cpp
new file mode 100644
index 000000000000..11636b31959f
--- /dev/null
+++ b/utils/stacktrace.cpp
@@ -0,0 +1,370 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/stacktrace.hpp"
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/resource.h>
+
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "utils/datetime.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/process/executor.ipp"
+#include "utils/process/operations.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+
+namespace datetime = utils::datetime;
+namespace executor = utils::process::executor;
+namespace fs = utils::fs;
+namespace process = utils::process;
+
+using utils::none;
+using utils::optional;
+
+
+/// Built-in path to GDB.
+///
+/// This is the value that should be passed to the find_gdb() function. If this
+/// is an absolute path, then we use the binary specified by the variable; if it
+/// is a relative path, we look for the binary in the path.
+///
+/// Test cases can override the value of this built-in constant to unit-test the
+/// behavior of the functions below.
+const char* utils::builtin_gdb = GDB;
+
+
+/// Maximum time the external GDB process is allowed to run for.
+datetime::delta utils::gdb_timeout(60, 0);
+
+
+namespace {
+
+
+/// Maximum length of the core file name, if known.
+///
+/// Some operating systems impose a maximum length on the basename of the core
+/// file. If MAXCOMLEN is defined, then we need to truncate the program name to
+/// this length before searching for the core file. If no such limit is known,
+/// this is infinite.
+static const std::string::size_type max_core_name_length =
+#if defined(MAXCOMLEN)
+ MAXCOMLEN
+#else
+ std::string::npos
+#endif
+ ;
+
+
+/// Functor to execute GDB in a subprocess.
+class run_gdb {
+ /// Path to the GDB binary to use.
+ const fs::path& _gdb;
+
+ /// Path to the program being debugged.
+ const fs::path& _program;
+
+ /// Path to the dumped core.
+ const fs::path& _core_name;
+
+public:
+ /// Constructs the functor.
+ ///
+ /// \param gdb_ Path to the GDB binary to use.
+ /// \param program_ Path to the program being debugged. Can be relative to
+ /// the given work directory.
+ /// \param core_name_ Path to the dumped core. Use find_core() to deduce
+ /// a valid candidate. Can be relative to the given work directory.
+ run_gdb(const fs::path& gdb_, const fs::path& program_,
+ const fs::path& core_name_) :
+ _gdb(gdb_), _program(program_), _core_name(core_name_)
+ {
+ }
+
+ /// Executes GDB.
+ ///
+ /// \param control_directory Directory where we can store control files to
+ /// not clobber any files created by the program being debugged.
+ void
+ operator()(const fs::path& control_directory)
+ {
+ const fs::path gdb_script_path = control_directory / "gdb.script";
+
+ // Old versions of GDB, such as the one shipped by FreeBSD as of
+ // 11.0-CURRENT on 2014-11-26, do not support scripts on the command
+ // line via the '-ex' flag. Instead, we have to create a script file
+ // and use that instead.
+ std::ofstream gdb_script(gdb_script_path.c_str());
+ if (!gdb_script) {
+ std::cerr << "Cannot create GDB script\n";
+ ::_exit(EXIT_FAILURE);
+ }
+ gdb_script << "backtrace\n";
+ gdb_script.close();
+
+ utils::unsetenv("TERM");
+
+ std::vector< std::string > args;
+ args.push_back("-batch");
+ args.push_back("-q");
+ args.push_back("-x");
+ args.push_back(gdb_script_path.str());
+ args.push_back(_program.str());
+ args.push_back(_core_name.str());
+
+ // Force all GDB output to go to stderr. We print messages to stderr
+ // when grabbing the stacktrace and we do not want GDB's output to end
+ // up split in two different files.
+ if (::dup2(STDERR_FILENO, STDOUT_FILENO) == -1) {
+ std::cerr << "Cannot redirect stdout to stderr\n";
+ ::_exit(EXIT_FAILURE);
+ }
+
+ process::exec(_gdb, args);
+ }
+};
+
+
+} // anonymous namespace
+
+
+/// Looks for the path to the GDB binary.
+///
+/// \return The absolute path to the GDB binary if any, otherwise none. Note
+/// that the returned path may or may not be valid: there is no guarantee that
+/// the path exists and is executable.
+optional< fs::path >
+utils::find_gdb(void)
+{
+ if (std::strlen(builtin_gdb) == 0) {
+ LW("The builtin path to GDB is bogus, which probably indicates a bug "
+ "in the build system; cannot gather stack traces");
+ return none;
+ }
+
+ const fs::path gdb(builtin_gdb);
+ if (gdb.is_absolute())
+ return utils::make_optional(gdb);
+ else
+ return fs::find_in_path(gdb.c_str());
+}
+
+
+/// Looks for a core file for the given program.
+///
+/// \param program The name of the binary that generated the core file. Can be
+/// either absolute or relative.
+/// \param status The exit status of the program. This is necessary to gather
+/// the PID.
+/// \param work_directory The directory from which the program was run.
+///
+/// \return The path to the core file, if found; otherwise none.
+optional< fs::path >
+utils::find_core(const fs::path& program, const process::status& status,
+ const fs::path& work_directory)
+{
+ std::vector< fs::path > candidates;
+
+ candidates.push_back(work_directory /
+ (program.leaf_name().substr(0, max_core_name_length) + ".core"));
+ if (program.is_absolute()) {
+ candidates.push_back(program.branch_path() /
+ (program.leaf_name().substr(0, max_core_name_length) + ".core"));
+ }
+ candidates.push_back(work_directory / (F("core.%s") % status.dead_pid()));
+ candidates.push_back(fs::path("/cores") /
+ (F("core.%s") % status.dead_pid()));
+
+ for (std::vector< fs::path >::const_iterator iter = candidates.begin();
+ iter != candidates.end(); ++iter) {
+ if (fs::exists(*iter)) {
+ LD(F("Attempting core file candidate %s: found") % *iter);
+ return utils::make_optional(*iter);
+ } else {
+ LD(F("Attempting core file candidate %s: not found") % *iter);
+ }
+ }
+ return none;
+}
+
+
+/// Raises core size limit to its possible maximum.
+///
+/// This is a best-effort operation. There is no guarantee that the operation
+/// will yield a large-enough limit to generate any possible core file.
+///
+/// \return True if the core size could be unlimited; false otherwise.
+bool
+utils::unlimit_core_size(void)
+{
+ bool ok;
+
+ struct ::rlimit rl;
+ if (::getrlimit(RLIMIT_CORE, &rl) == -1) {
+ const int original_errno = errno;
+ LW(F("getrlimit should not have failed but got: %s") %
+ std::strerror(original_errno));
+ ok = false;
+ } else {
+ if (rl.rlim_max == 0) {
+ LW("getrlimit returned 0 for RLIMIT_CORE rlim_max; cannot raise "
+ "soft core limit");
+ ok = false;
+ } else {
+ rl.rlim_cur = rl.rlim_max;
+ LD(F("Raising soft core size limit to %s (hard value)") %
+ rl.rlim_cur);
+ if (::setrlimit(RLIMIT_CORE, &rl) == -1) {
+ const int original_errno = errno;
+ LW(F("setrlimit should not have failed but got: %s") %
+ std::strerror(original_errno));
+ ok = false;
+ } else {
+ ok = true;
+ }
+ }
+ }
+
+ return ok;
+}
+
+
+/// Gathers a stacktrace of a crashed program.
+///
+/// \param program The name of the binary that crashed and dumped a core file.
+/// Can be either absolute or relative.
+/// \param executor_handle The executor handler to get the status from and
+/// gdb handler from.
+/// \param exit_handle The exit handler to stream additional diagnostic
+/// information from (stderr) and for redirecting to additional
+/// information to gdb from.
+///
+/// \post If anything goes wrong, the diagnostic messages are written to the
+/// output. This function should not throw.
+void
+utils::dump_stacktrace(const fs::path& program,
+ executor::executor_handle& executor_handle,
+ const executor::exit_handle& exit_handle)
+{
+ PRE(exit_handle.status());
+ const process::status& status = exit_handle.status().get();
+ PRE(status.signaled() && status.coredump());
+
+ std::ofstream gdb_err(exit_handle.stderr_file().c_str(), std::ios::app);
+ if (!gdb_err) {
+ LW(F("Failed to open %s to append GDB's output") %
+ exit_handle.stderr_file());
+ return;
+ }
+
+ gdb_err << F("Process with PID %s exited with signal %s and dumped core; "
+ "attempting to gather stack trace\n") %
+ status.dead_pid() % status.termsig();
+
+ const optional< fs::path > gdb = utils::find_gdb();
+ if (!gdb) {
+ gdb_err << F("Cannot find GDB binary; builtin was '%s'\n") %
+ builtin_gdb;
+ return;
+ }
+
+ const optional< fs::path > core_file = find_core(
+ program, status, exit_handle.work_directory());
+ if (!core_file) {
+ gdb_err << F("Cannot find any core file\n");
+ return;
+ }
+
+ gdb_err.flush();
+ const executor::exec_handle exec_handle =
+ executor_handle.spawn_followup(
+ run_gdb(gdb.get(), program, core_file.get()),
+ exit_handle, gdb_timeout);
+ const executor::exit_handle gdb_exit_handle =
+ executor_handle.wait(exec_handle);
+
+ const optional< process::status >& gdb_status = gdb_exit_handle.status();
+ if (!gdb_status) {
+ gdb_err << "GDB timed out\n";
+ } else {
+ if (gdb_status.get().exited() &&
+ gdb_status.get().exitstatus() == EXIT_SUCCESS) {
+ gdb_err << "GDB exited successfully\n";
+ } else {
+ gdb_err << "GDB failed; see output above for details\n";
+ }
+ }
+}
+
+
+/// Gathers a stacktrace of a program if it crashed.
+///
+/// This is just a convenience function to allow appending the stacktrace to an
+/// existing file and to permit reusing the status as returned by auxiliary
+/// process-spawning functions.
+///
+/// \param program The name of the binary that crashed and dumped a core file.
+/// Can be either absolute or relative.
+/// \param executor_handle The executor handler to get the status from and
+/// gdb handler from.
+/// \param exit_handle The exit handler to stream additional diagnostic
+/// information from (stderr) and for redirecting to additional
+/// information to gdb from.
+///
+/// \throw std::runtime_error If the output file cannot be opened.
+///
+/// \post If anything goes wrong with the stack gatheringq, the diagnostic
+/// messages are written to the output.
+void
+utils::dump_stacktrace_if_available(const fs::path& program,
+ executor::executor_handle& executor_handle,
+ const executor::exit_handle& exit_handle)
+{
+ const optional< process::status >& status = exit_handle.status();
+ if (!status || !status.get().signaled() || !status.get().coredump())
+ return;
+
+ dump_stacktrace(program, executor_handle, exit_handle);
+}
diff --git a/utils/stacktrace.hpp b/utils/stacktrace.hpp
new file mode 100644
index 000000000000..13648e2c71cb
--- /dev/null
+++ b/utils/stacktrace.hpp
@@ -0,0 +1,68 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/stacktrace.hpp
+/// Utilities to gather a stacktrace of a crashing binary.
+
+#if !defined(ENGINE_STACKTRACE_HPP)
+#define ENGINE_STACKTRACE_HPP
+
+#include <ostream>
+
+#include "utils/datetime_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+#include "utils/process/executor_fwd.hpp"
+#include "utils/process/status_fwd.hpp"
+
+namespace utils {
+
+
+extern const char* builtin_gdb;
+extern utils::datetime::delta gdb_timeout;
+
+utils::optional< utils::fs::path > find_gdb(void);
+
+utils::optional< utils::fs::path > find_core(const utils::fs::path&,
+ const utils::process::status&,
+ const utils::fs::path&);
+
+bool unlimit_core_size(void);
+
+void dump_stacktrace(const utils::fs::path&,
+ utils::process::executor::executor_handle&,
+ const utils::process::executor::exit_handle&);
+
+void dump_stacktrace_if_available(const utils::fs::path&,
+ utils::process::executor::executor_handle&,
+ const utils::process::executor::exit_handle&);
+
+
+} // namespace utils
+
+#endif // !defined(ENGINE_STACKTRACE_HPP)
diff --git a/utils/stacktrace_helper.cpp b/utils/stacktrace_helper.cpp
new file mode 100644
index 000000000000..f01e8c809797
--- /dev/null
+++ b/utils/stacktrace_helper.cpp
@@ -0,0 +1,36 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <cstdlib>
+
+
+int
+main(void)
+{
+ std::abort();
+}
diff --git a/utils/stacktrace_test.cpp b/utils/stacktrace_test.cpp
new file mode 100644
index 000000000000..ca87e7087f5a
--- /dev/null
+++ b/utils/stacktrace_test.cpp
@@ -0,0 +1,620 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/stacktrace.hpp"
+
+extern "C" {
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <iostream>
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/env.hpp"
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+#include "utils/process/executor.ipp"
+#include "utils/process/child.ipp"
+#include "utils/process/operations.hpp"
+#include "utils/process/status.hpp"
+#include "utils/sanity.hpp"
+#include "utils/test_utils.ipp"
+
+namespace datetime = utils::datetime;
+namespace executor = utils::process::executor;
+namespace fs = utils::fs;
+namespace process = utils::process;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Functor to execute a binary in a subprocess.
+///
+/// The provided binary is copied to the current work directory before being
+/// executed and the copy is given the name chosen by the user. The copy is
+/// necessary so that we have a deterministic location for where core files may
+/// be dumped (if they happen to be dumped in the current directory).
+class crash_me {
+ /// Path to the binary to execute.
+ const fs::path _binary;
+
+ /// Name of the binary after being copied.
+ const fs::path _copy_name;
+
+public:
+ /// Constructor.
+ ///
+ /// \param binary_ Path to binary to execute.
+ /// \param copy_name_ Name of the binary after being copied. If empty,
+ /// use the leaf name of binary_.
+ explicit crash_me(const fs::path& binary_,
+ const std::string& copy_name_ = "") :
+ _binary(binary_),
+ _copy_name(copy_name_.empty() ? binary_.leaf_name() : copy_name_)
+ {
+ }
+
+ /// Runs the binary.
+ void
+ operator()(void) const UTILS_NORETURN
+ {
+ atf::utils::copy_file(_binary.str(), _copy_name.str());
+
+ const std::vector< std::string > args;
+ process::exec(_copy_name, args);
+ }
+
+ /// Runs the binary.
+ ///
+ /// This interface is exposed to support passing crash_me to the executor.
+ void
+ operator()(const fs::path& /* control_directory */) const
+ UTILS_NORETURN
+ {
+ (*this)(); // Delegate to ensure the two entry points remain in sync.
+ }
+};
+
+
+static void child_exit(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that exits cleanly.
+static void
+child_exit(const fs::path& /* control_directory */)
+{
+ ::_exit(EXIT_SUCCESS);
+}
+
+
+static void child_pause(const fs::path&) UTILS_NORETURN;
+
+
+/// Subprocess that just blocks.
+static void
+child_pause(const fs::path& /* control_directory */)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ for (;;) {
+ ::sigsuspend(&mask);
+ }
+ std::abort();
+}
+
+
+/// Generates a core dump, if possible.
+///
+/// \post If this fails to generate a core file, the test case is marked as
+/// skipped. The caller can rely on this when attempting further checks on the
+/// core dump by assuming that the core dump exists somewhere.
+///
+/// \param test_case Pointer to the caller test case, needed to obtain the path
+/// to the source directory.
+/// \param base_name Name of the binary to execute, which will be a copy of a
+/// helper binary that always crashes. This name should later be part of
+/// the core filename.
+///
+/// \return The status of the crashed binary.
+static process::status
+generate_core(const atf::tests::tc* test_case, const char* base_name)
+{
+ utils::prepare_coredump_test(test_case);
+
+ const fs::path helper = fs::path(test_case->get_config_var("srcdir")) /
+ "stacktrace_helper";
+
+ const process::status status = process::child::fork_files(
+ crash_me(helper, base_name),
+ fs::path("unused.out"), fs::path("unused.err"))->wait();
+ ATF_REQUIRE(status.signaled());
+ if (!status.coredump())
+ ATF_SKIP("Test failed to generate core dump");
+ return status;
+}
+
+
+/// Generates a core dump, if possible.
+///
+/// \post If this fails to generate a core file, the test case is marked as
+/// skipped. The caller can rely on this when attempting further checks on the
+/// core dump by assuming that the core dump exists somewhere.
+///
+/// \param test_case Pointer to the caller test case, needed to obtain the path
+/// to the source directory.
+/// \param base_name Name of the binary to execute, which will be a copy of a
+/// helper binary that always crashes. This name should later be part of
+/// the core filename.
+/// \param executor_handle Executor to use to generate the core dump.
+///
+/// \return The exit handle of the subprocess so that a stacktrace can be
+/// executed reusing this context later on.
+static executor::exit_handle
+generate_core(const atf::tests::tc* test_case, const char* base_name,
+ executor::executor_handle& executor_handle)
+{
+ utils::prepare_coredump_test(test_case);
+
+ const fs::path helper = fs::path(test_case->get_config_var("srcdir")) /
+ "stacktrace_helper";
+
+ const executor::exec_handle exec_handle = executor_handle.spawn(
+ crash_me(helper, base_name), datetime::delta(60, 0), none, none, none);
+ const executor::exit_handle exit_handle = executor_handle.wait(exec_handle);
+
+ if (!exit_handle.status())
+ ATF_SKIP("Test failed to generate core dump (timed out)");
+ const process::status& status = exit_handle.status().get();
+ ATF_REQUIRE(status.signaled());
+ if (!status.coredump())
+ ATF_SKIP("Test failed to generate core dump");
+
+ return exit_handle;
+}
+
+
+/// Creates a script.
+///
+/// \param script Path to the script to create.
+/// \param contents Contents of the script.
+static void
+create_script(const char* script, const std::string& contents)
+{
+ atf::utils::create_file(script, "#! /bin/sh\n\n" + contents);
+ ATF_REQUIRE(::chmod(script, 0755) != -1);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unlimit_core_size);
+ATF_TEST_CASE_BODY(unlimit_core_size)
+{
+ utils::require_run_coredump_tests(this);
+
+ struct rlimit rl;
+ rl.rlim_cur = 0;
+ rl.rlim_max = RLIM_INFINITY;
+ if (::setrlimit(RLIMIT_CORE, &rl) == -1)
+ skip("Failed to lower the core size limit");
+
+ ATF_REQUIRE(utils::unlimit_core_size());
+
+ const fs::path helper = fs::path(get_config_var("srcdir")) /
+ "stacktrace_helper";
+ const process::status status = process::child::fork_files(
+ crash_me(helper),
+ fs::path("unused.out"), fs::path("unused.err"))->wait();
+ ATF_REQUIRE(status.signaled());
+ if (!status.coredump())
+ fail("Core not dumped as expected");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unlimit_core_size__hard_is_zero);
+ATF_TEST_CASE_BODY(unlimit_core_size__hard_is_zero)
+{
+ utils::require_run_coredump_tests(this);
+
+ struct rlimit rl;
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+ if (::setrlimit(RLIMIT_CORE, &rl) == -1)
+ skip("Failed to lower the core size limit");
+
+ ATF_REQUIRE(!utils::unlimit_core_size());
+
+ const fs::path helper = fs::path(get_config_var("srcdir")) /
+ "stacktrace_helper";
+ const process::status status = process::child::fork_files(
+ crash_me(helper),
+ fs::path("unused.out"), fs::path("unused.err"))->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE(!status.coredump());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__use_builtin);
+ATF_TEST_CASE_BODY(find_gdb__use_builtin)
+{
+ utils::builtin_gdb = "/path/to/gdb";
+ optional< fs::path > gdb = utils::find_gdb();
+ ATF_REQUIRE(gdb);
+ ATF_REQUIRE_EQ("/path/to/gdb", gdb.get().str());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__search_builtin__ok);
+ATF_TEST_CASE_BODY(find_gdb__search_builtin__ok)
+{
+ atf::utils::create_file("custom-name", "");
+ ATF_REQUIRE(::chmod("custom-name", 0755) != -1);
+ const fs::path exp_gdb = fs::path("custom-name").to_absolute();
+
+ utils::setenv("PATH", "/non-existent/location:.:/bin");
+
+ utils::builtin_gdb = "custom-name";
+ optional< fs::path > gdb = utils::find_gdb();
+ ATF_REQUIRE(gdb);
+ ATF_REQUIRE_EQ(exp_gdb, gdb.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__search_builtin__fail);
+ATF_TEST_CASE_BODY(find_gdb__search_builtin__fail)
+{
+ utils::setenv("PATH", ".");
+ utils::builtin_gdb = "foo";
+ optional< fs::path > gdb = utils::find_gdb();
+ ATF_REQUIRE(!gdb);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__bogus_value);
+ATF_TEST_CASE_BODY(find_gdb__bogus_value)
+{
+ utils::builtin_gdb = "";
+ optional< fs::path > gdb = utils::find_gdb();
+ ATF_REQUIRE(!gdb);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_core__found__short);
+ATF_TEST_CASE_BODY(find_core__found__short)
+{
+ const process::status status = generate_core(this, "short");
+ INV(status.coredump());
+ const optional< fs::path > core_name = utils::find_core(
+ fs::path("short"), status, fs::path("."));
+ if (!core_name)
+ fail("Core dumped, but no candidates found");
+ ATF_REQUIRE(core_name.get().str().find("core") != std::string::npos);
+ ATF_REQUIRE(fs::exists(core_name.get()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_core__found__long);
+ATF_TEST_CASE_BODY(find_core__found__long)
+{
+ const process::status status = generate_core(
+ this, "long-name-that-may-be-truncated-in-some-systems");
+ INV(status.coredump());
+ const optional< fs::path > core_name = utils::find_core(
+ fs::path("long-name-that-may-be-truncated-in-some-systems"),
+ status, fs::path("."));
+ if (!core_name)
+ fail("Core dumped, but no candidates found");
+ ATF_REQUIRE(core_name.get().str().find("core") != std::string::npos);
+ ATF_REQUIRE(fs::exists(core_name.get()));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find_core__not_found);
+ATF_TEST_CASE_BODY(find_core__not_found)
+{
+ const process::status status = process::status::fake_signaled(SIGILL, true);
+ const optional< fs::path > core_name = utils::find_core(
+ fs::path("missing"), status, fs::path("."));
+ if (core_name)
+ fail("Core not dumped, but candidate found: " + core_name.get().str());
+}
+
+
+ATF_TEST_CASE(dump_stacktrace__integration);
+ATF_TEST_CASE_HEAD(dump_stacktrace__integration)
+{
+ set_md_var("require.progs", utils::builtin_gdb);
+}
+ATF_TEST_CASE_BODY(dump_stacktrace__integration)
+{
+ executor::executor_handle handle = executor::setup();
+
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+ INV(exit_handle.status());
+ INV(exit_handle.status().get().coredump());
+
+ std::ostringstream output;
+ utils::dump_stacktrace(fs::path("short"), handle, exit_handle);
+
+ // It is hard to validate the execution of an arbitrary GDB of which we do
+ // not know anything. Just assume that the backtrace, at the very least,
+ // prints a couple of frame identifiers.
+ ATF_REQUIRE(!atf::utils::grep_file("#0", exit_handle.stdout_file().str()));
+ ATF_REQUIRE( atf::utils::grep_file("#0", exit_handle.stderr_file().str()));
+ ATF_REQUIRE(!atf::utils::grep_file("#1", exit_handle.stdout_file().str()));
+ ATF_REQUIRE( atf::utils::grep_file("#1", exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__ok);
+ATF_TEST_CASE_BODY(dump_stacktrace__ok)
+{
+ utils::setenv("PATH", ".");
+ create_script("fake-gdb", "echo 'frame 1'; echo 'frame 2'; "
+ "echo 'some warning' 1>&2; exit 0");
+ utils::builtin_gdb = "fake-gdb";
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+ INV(exit_handle.status());
+ INV(exit_handle.status().get().coredump());
+
+ utils::dump_stacktrace(fs::path("short"), handle, exit_handle);
+
+ // Note how all output is expected on stderr even for the messages that the
+ // script decided to send to stdout.
+ ATF_REQUIRE(atf::utils::grep_file("exited with signal [0-9]* and dumped",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("^frame 1$",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("^frame 2$",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("^some warning$",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("GDB exited successfully",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__cannot_find_core);
+ATF_TEST_CASE_BODY(dump_stacktrace__cannot_find_core)
+{
+ // Make sure we can find a GDB binary so that we don't fail the test for
+ // the wrong reason.
+ utils::setenv("PATH", ".");
+ utils::builtin_gdb = "fake-gdb";
+ atf::utils::create_file("fake-gdb", "unused");
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+
+ const optional< fs::path > core_name = utils::find_core(
+ fs::path("short"),
+ exit_handle.status().get(),
+ exit_handle.work_directory());
+ if (core_name) {
+ // This is needed even if we provide a different basename to
+ // dump_stacktrace below because the system policies may be generating
+ // core dumps by PID, not binary name.
+ std::cout << "Removing core dump: " << core_name << '\n';
+ fs::unlink(core_name.get());
+ }
+
+ utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
+
+ atf::utils::cat_file(exit_handle.stdout_file().str(), "stdout: ");
+ atf::utils::cat_file(exit_handle.stderr_file().str(), "stderr: ");
+ ATF_REQUIRE(atf::utils::grep_file("Cannot find any core file",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__cannot_find_gdb);
+ATF_TEST_CASE_BODY(dump_stacktrace__cannot_find_gdb)
+{
+ utils::setenv("PATH", ".");
+ utils::builtin_gdb = "missing-gdb";
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+
+ utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
+
+ ATF_REQUIRE(atf::utils::grep_file(
+ "Cannot find GDB binary; builtin was 'missing-gdb'",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__gdb_fail);
+ATF_TEST_CASE_BODY(dump_stacktrace__gdb_fail)
+{
+ utils::setenv("PATH", ".");
+ create_script("fake-gdb", "echo 'foo'; echo 'bar' 1>&2; exit 1");
+ const std::string gdb = (fs::current_path() / "fake-gdb").str();
+ utils::builtin_gdb = gdb.c_str();
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+
+ atf::utils::create_file((exit_handle.work_directory() / "fake.core").str(),
+ "Invalid core file, but not read");
+ utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
+
+ ATF_REQUIRE(atf::utils::grep_file("^foo$",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("^bar$",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("GDB failed; see output above",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__gdb_timeout);
+ATF_TEST_CASE_BODY(dump_stacktrace__gdb_timeout)
+{
+ utils::setenv("PATH", ".");
+ create_script("fake-gdb", "while :; do sleep 1; done");
+ const std::string gdb = (fs::current_path() / "fake-gdb").str();
+ utils::builtin_gdb = gdb.c_str();
+ utils::gdb_timeout = datetime::delta(1, 0);
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+
+ atf::utils::create_file((exit_handle.work_directory() / "fake.core").str(),
+ "Invalid core file, but not read");
+ utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
+
+ ATF_REQUIRE(atf::utils::grep_file("GDB timed out",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__append);
+ATF_TEST_CASE_BODY(dump_stacktrace_if_available__append)
+{
+ utils::setenv("PATH", ".");
+ create_script("fake-gdb", "echo 'frame 1'; exit 0");
+ utils::builtin_gdb = "fake-gdb";
+
+ executor::executor_handle handle = executor::setup();
+ executor::exit_handle exit_handle = generate_core(this, "short", handle);
+
+ atf::utils::create_file(exit_handle.stdout_file().str(), "Pre-stdout");
+ atf::utils::create_file(exit_handle.stderr_file().str(), "Pre-stderr");
+
+ utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
+
+ ATF_REQUIRE(atf::utils::grep_file("Pre-stdout",
+ exit_handle.stdout_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("Pre-stderr",
+ exit_handle.stderr_file().str()));
+ ATF_REQUIRE(atf::utils::grep_file("frame 1",
+ exit_handle.stderr_file().str()));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__no_status);
+ATF_TEST_CASE_BODY(dump_stacktrace_if_available__no_status)
+{
+ executor::executor_handle handle = executor::setup();
+ const executor::exec_handle exec_handle = handle.spawn(
+ child_pause, datetime::delta(0, 100000), none, none, none);
+ executor::exit_handle exit_handle = handle.wait(exec_handle);
+ INV(!exit_handle.status());
+
+ utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
+ ATF_REQUIRE(atf::utils::compare_file(exit_handle.stdout_file().str(), ""));
+ ATF_REQUIRE(atf::utils::compare_file(exit_handle.stderr_file().str(), ""));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__no_coredump);
+ATF_TEST_CASE_BODY(dump_stacktrace_if_available__no_coredump)
+{
+ executor::executor_handle handle = executor::setup();
+ const executor::exec_handle exec_handle = handle.spawn(
+ child_exit, datetime::delta(60, 0), none, none, none);
+ executor::exit_handle exit_handle = handle.wait(exec_handle);
+ INV(exit_handle.status());
+ INV(exit_handle.status().get().exited());
+ INV(exit_handle.status().get().exitstatus() == EXIT_SUCCESS);
+
+ utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
+ ATF_REQUIRE(atf::utils::compare_file(exit_handle.stdout_file().str(), ""));
+ ATF_REQUIRE(atf::utils::compare_file(exit_handle.stderr_file().str(), ""));
+
+ exit_handle.cleanup();
+ handle.cleanup();
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, unlimit_core_size);
+ ATF_ADD_TEST_CASE(tcs, unlimit_core_size__hard_is_zero);
+
+ ATF_ADD_TEST_CASE(tcs, find_gdb__use_builtin);
+ ATF_ADD_TEST_CASE(tcs, find_gdb__search_builtin__ok);
+ ATF_ADD_TEST_CASE(tcs, find_gdb__search_builtin__fail);
+ ATF_ADD_TEST_CASE(tcs, find_gdb__bogus_value);
+
+ ATF_ADD_TEST_CASE(tcs, find_core__found__short);
+ ATF_ADD_TEST_CASE(tcs, find_core__found__long);
+ ATF_ADD_TEST_CASE(tcs, find_core__not_found);
+
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__integration);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__ok);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__cannot_find_core);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__cannot_find_gdb);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__gdb_fail);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace__gdb_timeout);
+
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__append);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__no_status);
+ ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__no_coredump);
+}
diff --git a/utils/stream.cpp b/utils/stream.cpp
new file mode 100644
index 000000000000..ee3ab417f753
--- /dev/null
+++ b/utils/stream.cpp
@@ -0,0 +1,149 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/stream.hpp"
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Constant that represents the path to stdout.
+static const fs::path stdout_path("/dev/stdout");
+
+
+/// Constant that represents the path to stderr.
+static const fs::path stderr_path("/dev/stderr");
+
+
+} // anonymous namespace
+
+
+/// Opens a new file for output, respecting the stdout and stderr streams.
+///
+/// \param path The path to the output file to be created.
+///
+/// \return A pointer to a new output stream.
+std::auto_ptr< std::ostream >
+utils::open_ostream(const fs::path& path)
+{
+ std::auto_ptr< std::ostream > out;
+ if (path == stdout_path) {
+ out.reset(new std::ofstream());
+ out->copyfmt(std::cout);
+ out->clear(std::cout.rdstate());
+ out->rdbuf(std::cout.rdbuf());
+ } else if (path == stderr_path) {
+ out.reset(new std::ofstream());
+ out->copyfmt(std::cerr);
+ out->clear(std::cerr.rdstate());
+ out->rdbuf(std::cerr.rdbuf());
+ } else {
+ out.reset(new std::ofstream(path.c_str()));
+ if (!(*out)) {
+ throw std::runtime_error(F("Cannot open output file %s") % path);
+ }
+ }
+ INV(out.get() != NULL);
+ return out;
+}
+
+
+/// Gets the length of a stream.
+///
+/// \param is The input stream for which to calculate its length.
+///
+/// \return The length of the stream. This is of size_t type instead of
+/// directly std::streampos to simplify the caller. Some systems do not
+/// support comparing a std::streampos directly to an integer (see
+/// NetBSD 1.5.x), which is what we often want to do.
+///
+/// \throw std::exception If calculating the length fails due to a stream error.
+std::size_t
+utils::stream_length(std::istream& is)
+{
+ const std::streampos current_pos = is.tellg();
+ try {
+ is.seekg(0, std::ios::end);
+ const std::streampos length = is.tellg();
+ is.seekg(current_pos, std::ios::beg);
+ return static_cast< std::size_t >(length);
+ } catch (...) {
+ is.seekg(current_pos, std::ios::beg);
+ throw;
+ }
+}
+
+
+/// Reads a whole file into memory.
+///
+/// \param path The file to read.
+///
+/// \return A plain string containing the raw contents of the file.
+///
+/// \throw std::runtime_error If the file cannot be opened.
+std::string
+utils::read_file(const fs::path& path)
+{
+ std::ifstream input(path.c_str());
+ if (!input)
+ throw std::runtime_error(F("Failed to open '%s' for read") % path);
+ return read_stream(input);
+}
+
+
+/// Reads the whole contents of a stream into memory.
+///
+/// \param input The input stream from which to read.
+///
+/// \return A plain string containing the raw contents of the file.
+std::string
+utils::read_stream(std::istream& input)
+{
+ std::ostringstream buffer;
+
+ char tmp[1024];
+ while (input.good()) {
+ input.read(tmp, sizeof(tmp));
+ if (input.good() || input.eof()) {
+ buffer.write(tmp, input.gcount());
+ }
+ }
+
+ return buffer.str();
+}
diff --git a/utils/stream.hpp b/utils/stream.hpp
new file mode 100644
index 000000000000..5c9316e72810
--- /dev/null
+++ b/utils/stream.hpp
@@ -0,0 +1,57 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/stream.hpp
+/// Stream manipulation utilities.
+///
+/// Note that file-manipulation utilities live in utils::fs instead. The
+/// utilities here deal with already-open streams.
+
+#if !defined(UTILS_STREAM_HPP)
+#define UTILS_STREAM_HPP
+
+#include <cstddef>
+#include <istream>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+
+
+std::auto_ptr< std::ostream > open_ostream(const utils::fs::path&);
+std::size_t stream_length(std::istream&);
+std::string read_file(const utils::fs::path&);
+std::string read_stream(std::istream&);
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_STREAM_HPP)
diff --git a/utils/stream_test.cpp b/utils/stream_test.cpp
new file mode 100644
index 000000000000..7c4f3b5c6b4a
--- /dev/null
+++ b/utils/stream_test.cpp
@@ -0,0 +1,157 @@
+// Copyright 2011 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/stream.hpp"
+
+#include <cstdlib>
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+
+namespace fs = utils::fs;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__stdout);
+ATF_TEST_CASE_BODY(open_ostream__stdout)
+{
+ const pid_t pid = atf::utils::fork();
+ if (pid == 0) {
+ std::auto_ptr< std::ostream > output = utils::open_ostream(
+ fs::path("/dev/stdout"));
+ (*output) << "Message to stdout\n";
+ output.reset();
+ std::exit(EXIT_SUCCESS);
+ }
+ atf::utils::wait(pid, EXIT_SUCCESS, "Message to stdout\n", "");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__stderr);
+ATF_TEST_CASE_BODY(open_ostream__stderr)
+{
+ const pid_t pid = atf::utils::fork();
+ if (pid == 0) {
+ std::auto_ptr< std::ostream > output = utils::open_ostream(
+ fs::path("/dev/stderr"));
+ (*output) << "Message to stderr\n";
+ output.reset();
+ std::exit(EXIT_SUCCESS);
+ }
+ atf::utils::wait(pid, EXIT_SUCCESS, "", "Message to stderr\n");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__other);
+ATF_TEST_CASE_BODY(open_ostream__other)
+{
+ const pid_t pid = atf::utils::fork();
+ if (pid == 0) {
+ std::auto_ptr< std::ostream > output = utils::open_ostream(
+ fs::path("some-file.txt"));
+ (*output) << "Message to other file\n";
+ output.reset();
+ std::exit(EXIT_SUCCESS);
+ }
+ atf::utils::wait(pid, EXIT_SUCCESS, "", "");
+ atf::utils::compare_file("some-file.txt", "Message to other file\n");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(stream_length__empty);
+ATF_TEST_CASE_BODY(stream_length__empty)
+{
+ std::istringstream input("");
+ ATF_REQUIRE_EQ(0, utils::stream_length(input));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(stream_length__some);
+ATF_TEST_CASE_BODY(stream_length__some)
+{
+ const std::string contents(8192, 'x');
+ std::istringstream input(contents);
+ ATF_REQUIRE_EQ(
+ contents.length(),
+ static_cast< std::string::size_type >(utils::stream_length(input)));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_file__ok);
+ATF_TEST_CASE_BODY(read_file__ok)
+{
+ const char* contents = "These are\nsome file contents";
+ atf::utils::create_file("input.txt", contents);
+ ATF_REQUIRE_EQ(contents, utils::read_file(fs::path("input.txt")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_file__missing_file);
+ATF_TEST_CASE_BODY(read_file__missing_file)
+{
+ ATF_REQUIRE_THROW_RE(std::runtime_error,
+ "Failed to open 'foo.txt' for read",
+ utils::read_file(fs::path("foo.txt")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_stream__empty);
+ATF_TEST_CASE_BODY(read_stream__empty)
+{
+ std::istringstream input("");
+ ATF_REQUIRE_EQ("", utils::read_stream(input));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(read_stream__some);
+ATF_TEST_CASE_BODY(read_stream__some)
+{
+ std::string contents;
+ for (int i = 0; i < 1000; i++)
+ contents += "abcdef";
+ std::istringstream input(contents);
+ ATF_REQUIRE_EQ(contents, utils::read_stream(input));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, open_ostream__stdout);
+ ATF_ADD_TEST_CASE(tcs, open_ostream__stderr);
+ ATF_ADD_TEST_CASE(tcs, open_ostream__other);
+
+ ATF_ADD_TEST_CASE(tcs, stream_length__empty);
+ ATF_ADD_TEST_CASE(tcs, stream_length__some);
+
+ ATF_ADD_TEST_CASE(tcs, read_file__ok);
+ ATF_ADD_TEST_CASE(tcs, read_file__missing_file);
+
+ ATF_ADD_TEST_CASE(tcs, read_stream__empty);
+ ATF_ADD_TEST_CASE(tcs, read_stream__some);
+}
diff --git a/utils/test_utils.ipp b/utils/test_utils.ipp
new file mode 100644
index 000000000000..f21d0f4cc172
--- /dev/null
+++ b/utils/test_utils.ipp
@@ -0,0 +1,113 @@
+// Copyright 2016 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/test_utils.ipp
+/// Provides test-only convenience utilities.
+
+#if defined(UTILS_TEST_UTILS_IPP)
+# error "utils/test_utils.hpp can only be included once"
+#endif
+#define UTILS_TEST_UTILS_IPP
+
+extern "C" {
+#include <sys/resource.h>
+}
+
+#include <cstdlib>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/stacktrace.hpp"
+#include "utils/text/operations.ipp"
+
+namespace utils {
+
+
+/// Tries to prevent dumping core if we do not need one on a crash.
+///
+/// This is a best-effort operation provided so that tests that will cause
+/// a crash do not collect an unnecessary core dump, which can be slow on
+/// some systems (e.g. on macOS).
+inline void
+avoid_coredump_on_crash(void)
+{
+ struct ::rlimit rl;
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+ if (::setrlimit(RLIMIT_CORE, &rl) == -1) {
+ std::cerr << "Failed to zero core size limit; may dump core\n";
+ }
+}
+
+
+inline void abort_without_coredump(void) UTILS_NORETURN;
+
+
+/// Aborts execution and tries to not dump core.
+///
+/// The coredump avoidance is a best-effort operation provided so that tests
+/// that will cause a crash do not collect an unnecessary core dump, which can
+/// be slow on some systems (e.g. on macOS).
+inline void
+abort_without_coredump(void)
+{
+ avoid_coredump_on_crash();
+ std::abort();
+}
+
+
+/// Skips the test if coredump tests have been disabled by the user.
+///
+/// \param tc The calling test.
+inline void
+require_run_coredump_tests(const atf::tests::tc* tc)
+{
+ if (tc->has_config_var("run_coredump_tests") &&
+ !text::to_type< bool >(tc->get_config_var("run_coredump_tests"))) {
+ tc->skip("run_coredump_tests=false; not running test");
+ }
+}
+
+
+/// Prepares the test so that it can dump core, or skips it otherwise.
+///
+/// \param tc The calling test.
+inline void
+prepare_coredump_test(const atf::tests::tc* tc)
+{
+ require_run_coredump_tests(tc);
+
+ if (!unlimit_core_size()) {
+ tc->skip("Cannot unlimit the core file size; check limits manually");
+ }
+}
+
+
+} // namespace utils
diff --git a/utils/text/Kyuafile b/utils/text/Kyuafile
new file mode 100644
index 000000000000..e4e870e9c648
--- /dev/null
+++ b/utils/text/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="operations_test"}
+atf_test_program{name="regex_test"}
+atf_test_program{name="table_test"}
+atf_test_program{name="templates_test"}
diff --git a/utils/text/Makefile.am.inc b/utils/text/Makefile.am.inc
new file mode 100644
index 000000000000..d474ae191bf5
--- /dev/null
+++ b/utils/text/Makefile.am.inc
@@ -0,0 +1,74 @@
+# Copyright 2012 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/text/exceptions.cpp
+libutils_a_SOURCES += utils/text/exceptions.hpp
+libutils_a_SOURCES += utils/text/operations.cpp
+libutils_a_SOURCES += utils/text/operations.hpp
+libutils_a_SOURCES += utils/text/operations.ipp
+libutils_a_SOURCES += utils/text/regex.cpp
+libutils_a_SOURCES += utils/text/regex.hpp
+libutils_a_SOURCES += utils/text/regex_fwd.hpp
+libutils_a_SOURCES += utils/text/table.cpp
+libutils_a_SOURCES += utils/text/table.hpp
+libutils_a_SOURCES += utils/text/table_fwd.hpp
+libutils_a_SOURCES += utils/text/templates.cpp
+libutils_a_SOURCES += utils/text/templates.hpp
+libutils_a_SOURCES += utils/text/templates_fwd.hpp
+
+if WITH_ATF
+tests_utils_textdir = $(pkgtestsdir)/utils/text
+
+tests_utils_text_DATA = utils/text/Kyuafile
+EXTRA_DIST += $(tests_utils_text_DATA)
+
+tests_utils_text_PROGRAMS = utils/text/exceptions_test
+utils_text_exceptions_test_SOURCES = utils/text/exceptions_test.cpp
+utils_text_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/operations_test
+utils_text_operations_test_SOURCES = utils/text/operations_test.cpp
+utils_text_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/regex_test
+utils_text_regex_test_SOURCES = utils/text/regex_test.cpp
+utils_text_regex_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_regex_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/table_test
+utils_text_table_test_SOURCES = utils/text/table_test.cpp
+utils_text_table_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_table_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/templates_test
+utils_text_templates_test_SOURCES = utils/text/templates_test.cpp
+utils_text_templates_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_templates_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/text/exceptions.cpp b/utils/text/exceptions.cpp
new file mode 100644
index 000000000000..1692cfea7edb
--- /dev/null
+++ b/utils/text/exceptions.cpp
@@ -0,0 +1,91 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::regex_error::regex_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::regex_error::~regex_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::syntax_error::syntax_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::syntax_error::~syntax_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::value_error::value_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::value_error::~value_error(void) throw()
+{
+}
diff --git a/utils/text/exceptions.hpp b/utils/text/exceptions.hpp
new file mode 100644
index 000000000000..da0cfd98fb88
--- /dev/null
+++ b/utils/text/exceptions.hpp
@@ -0,0 +1,77 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/exceptions.hpp
+/// Exception types raised by the text module.
+
+#if !defined(UTILS_TEXT_EXCEPTIONS_HPP)
+#define UTILS_TEXT_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+namespace utils {
+namespace text {
+
+
+/// Base exceptions for text errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exception denoting an error in a regular expression.
+class regex_error : public error {
+public:
+ explicit regex_error(const std::string&);
+ ~regex_error(void) throw();
+};
+
+
+/// Exception denoting an error while parsing templates.
+class syntax_error : public error {
+public:
+ explicit syntax_error(const std::string&);
+ ~syntax_error(void) throw();
+};
+
+
+/// Exception denoting an error in a text value format.
+class value_error : public error {
+public:
+ explicit value_error(const std::string&);
+ ~value_error(void) throw();
+};
+
+
+} // namespace text
+} // namespace utils
+
+
+#endif // !defined(UTILS_TEXT_EXCEPTIONS_HPP)
diff --git a/utils/text/exceptions_test.cpp b/utils/text/exceptions_test.cpp
new file mode 100644
index 000000000000..1d3c3910900a
--- /dev/null
+++ b/utils/text/exceptions_test.cpp
@@ -0,0 +1,76 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+namespace text = utils::text;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const text::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(regex_error);
+ATF_TEST_CASE_BODY(regex_error)
+{
+ const text::regex_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_error);
+ATF_TEST_CASE_BODY(syntax_error)
+{
+ const text::syntax_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ const text::value_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, regex_error);
+ ATF_ADD_TEST_CASE(tcs, syntax_error);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/text/operations.cpp b/utils/text/operations.cpp
new file mode 100644
index 000000000000..5a4345d979c7
--- /dev/null
+++ b/utils/text/operations.cpp
@@ -0,0 +1,261 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/operations.ipp"
+
+#include <sstream>
+
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace text = utils::text;
+
+
+/// Replaces XML special characters from an input string.
+///
+/// The list of XML special characters is specified here:
+/// http://www.w3.org/TR/xml11/#charsets
+///
+/// \param in The input to quote.
+///
+/// \return A quoted string without any XML special characters.
+std::string
+text::escape_xml(const std::string& in)
+{
+ std::ostringstream quoted;
+
+ for (std::string::const_iterator it = in.begin();
+ it != in.end(); ++it) {
+ unsigned char c = (unsigned char)*it;
+ if (c == '"') {
+ quoted << "&quot;";
+ } else if (c == '&') {
+ quoted << "&amp;";
+ } else if (c == '<') {
+ quoted << "&lt;";
+ } else if (c == '>') {
+ quoted << "&gt;";
+ } else if (c == '\'') {
+ quoted << "&apos;";
+ } else if ((c >= 0x01 && c <= 0x08) ||
+ (c >= 0x0B && c <= 0x0C) ||
+ (c >= 0x0E && c <= 0x1F) ||
+ (c >= 0x7F && c <= 0x84) ||
+ (c >= 0x86 && c <= 0x9F)) {
+ // for RestrictedChar characters, escape them
+ // as '&amp;#[decimal ASCII value];'
+ // so that in the XML file we will see the escaped
+ // character.
+ quoted << "&amp;#" << static_cast< std::string::size_type >(*it)
+ << ";";
+ } else {
+ quoted << *it;
+ }
+ }
+ return quoted.str();
+}
+
+
+/// Surrounds a string with quotes, escaping the quote itself if needed.
+///
+/// \param text The string to quote.
+/// \param quote The quote character to use.
+///
+/// \return The quoted string.
+std::string
+text::quote(const std::string& text, const char quote)
+{
+ std::ostringstream quoted;
+ quoted << quote;
+
+ std::string::size_type start_pos = 0;
+ std::string::size_type last_pos = text.find(quote);
+ while (last_pos != std::string::npos) {
+ quoted << text.substr(start_pos, last_pos - start_pos) << '\\';
+ start_pos = last_pos;
+ last_pos = text.find(quote, start_pos + 1);
+ }
+ quoted << text.substr(start_pos);
+
+ quoted << quote;
+ return quoted.str();
+}
+
+
+/// Fills a paragraph to the specified length.
+///
+/// This preserves any sequence of spaces in the input and any possible
+/// newlines. Sequences of spaces may be split in half (and thus one space is
+/// lost), but the rest of the spaces will be preserved as either trailing or
+/// leading spaces.
+///
+/// \param input The string to refill.
+/// \param target_width The width to refill the paragraph to.
+///
+/// \return The refilled paragraph as a sequence of independent lines.
+std::vector< std::string >
+text::refill(const std::string& input, const std::size_t target_width)
+{
+ std::vector< std::string > output;
+
+ std::string::size_type start = 0;
+ while (start < input.length()) {
+ std::string::size_type width;
+ if (start + target_width >= input.length())
+ width = input.length() - start;
+ else {
+ if (input[start + target_width] == ' ') {
+ width = target_width;
+ } else {
+ const std::string::size_type pos = input.find_last_of(
+ " ", start + target_width - 1);
+ if (pos == std::string::npos || pos < start + 1) {
+ width = input.find_first_of(" ", start + target_width);
+ if (width == std::string::npos)
+ width = input.length() - start;
+ else
+ width -= start;
+ } else {
+ width = pos - start;
+ }
+ }
+ }
+ INV(width != std::string::npos);
+ INV(start + width <= input.length());
+ INV(input[start + width] == ' ' || input[start + width] == '\0');
+ output.push_back(input.substr(start, width));
+
+ start += width + 1;
+ }
+
+ if (input.empty()) {
+ INV(output.empty());
+ output.push_back("");
+ }
+
+ return output;
+}
+
+
+/// Fills a paragraph to the specified length.
+///
+/// See the documentation for refill() for additional details.
+///
+/// \param input The string to refill.
+/// \param target_width The width to refill the paragraph to.
+///
+/// \return The refilled paragraph as a string with embedded newlines.
+std::string
+text::refill_as_string(const std::string& input, const std::size_t target_width)
+{
+ return join(refill(input, target_width), "\n");
+}
+
+
+/// Replaces all occurrences of a substring in a string.
+///
+/// \param input The string in which to perform the replacement.
+/// \param search The pattern to be replaced.
+/// \param replacement The substring to replace search with.
+///
+/// \return A copy of input with the replacements performed.
+std::string
+text::replace_all(const std::string& input, const std::string& search,
+ const std::string& replacement)
+{
+ std::string output;
+
+ std::string::size_type pos, lastpos = 0;
+ while ((pos = input.find(search, lastpos)) != std::string::npos) {
+ output += input.substr(lastpos, pos - lastpos);
+ output += replacement;
+ lastpos = pos + search.length();
+ }
+ output += input.substr(lastpos);
+
+ return output;
+}
+
+
+/// Splits a string into different components.
+///
+/// \param str The string to split.
+/// \param delimiter The separator to use to split the words.
+///
+/// \return The different words in the input string as split by the provided
+/// delimiter.
+std::vector< std::string >
+text::split(const std::string& str, const char delimiter)
+{
+ std::vector< std::string > words;
+ if (!str.empty()) {
+ std::string::size_type pos = str.find(delimiter);
+ words.push_back(str.substr(0, pos));
+ while (pos != std::string::npos) {
+ ++pos;
+ const std::string::size_type next = str.find(delimiter, pos);
+ words.push_back(str.substr(pos, next - pos));
+ pos = next;
+ }
+ }
+ return words;
+}
+
+
+/// Converts a string to a boolean.
+///
+/// \param str The string to convert.
+///
+/// \return The converted string, if the input string was valid.
+///
+/// \throw std::value_error If the input string does not represent a valid
+/// boolean value.
+template<>
+bool
+text::to_type(const std::string& str)
+{
+ if (str == "true")
+ return true;
+ else if (str == "false")
+ return false;
+ else
+ throw value_error(F("Invalid boolean value '%s'") % str);
+}
+
+
+/// Identity function for to_type, for genericity purposes.
+///
+/// \param str The string to convert.
+///
+/// \return The input string.
+template<>
+std::string
+text::to_type(const std::string& str)
+{
+ return str;
+}
diff --git a/utils/text/operations.hpp b/utils/text/operations.hpp
new file mode 100644
index 000000000000..6d15be553b06
--- /dev/null
+++ b/utils/text/operations.hpp
@@ -0,0 +1,68 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/operations.hpp
+/// Utilities to manipulate strings.
+
+#if !defined(UTILS_TEXT_OPERATIONS_HPP)
+#define UTILS_TEXT_OPERATIONS_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+std::string escape_xml(const std::string&);
+std::string quote(const std::string&, const char);
+
+
+std::vector< std::string > refill(const std::string&, const std::size_t);
+std::string refill_as_string(const std::string&, const std::size_t);
+
+std::string replace_all(const std::string&, const std::string&,
+ const std::string&);
+
+template< typename Collection >
+std::string join(const Collection&, const std::string&);
+std::vector< std::string > split(const std::string&, const char);
+
+template< typename Type >
+Type to_type(const std::string&);
+template<>
+bool to_type(const std::string&);
+template<>
+std::string to_type(const std::string&);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_OPERATIONS_HPP)
diff --git a/utils/text/operations.ipp b/utils/text/operations.ipp
new file mode 100644
index 000000000000..511cd6840a08
--- /dev/null
+++ b/utils/text/operations.ipp
@@ -0,0 +1,91 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_TEXT_OPERATIONS_IPP)
+#define UTILS_TEXT_OPERATIONS_IPP
+
+#include "utils/text/operations.hpp"
+
+#include <sstream>
+
+#include "utils/text/exceptions.hpp"
+
+
+/// Concatenates a collection of strings into a single string.
+///
+/// \param strings The collection of strings to concatenate. If the collection
+/// is unordered, the ordering in the output is undefined.
+/// \param delimiter The delimiter to use to separate the strings.
+///
+/// \return The concatenated strings.
+template< typename Collection >
+std::string
+utils::text::join(const Collection& strings, const std::string& delimiter)
+{
+ std::ostringstream output;
+ if (strings.size() > 1) {
+ for (typename Collection::const_iterator iter = strings.begin();
+ iter != --strings.end(); ++iter)
+ output << (*iter) << delimiter;
+ }
+ if (strings.size() > 0)
+ output << *(--strings.end());
+ return output.str();
+}
+
+
+/// Converts a string to a native type.
+///
+/// \tparam Type The type to convert the string to. An input stream operator
+/// must exist to extract such a type from an std::istream.
+/// \param str The string to convert.
+///
+/// \return The converted string, if the input string was valid.
+///
+/// \throw std::value_error If the input string does not represent a valid
+/// target type. This exception does not include any details, so the caller
+/// must take care to re-raise it with appropriate details.
+template< typename Type >
+Type
+utils::text::to_type(const std::string& str)
+{
+ if (str.empty())
+ throw text::value_error("Empty string");
+ if (str[0] == ' ')
+ throw text::value_error("Invalid value");
+
+ std::istringstream input(str);
+ Type value;
+ input >> value;
+ if (!input.eof() || input.bad() || input.fail())
+ throw text::value_error("Invalid value");
+ return value;
+}
+
+
+#endif // !defined(UTILS_TEXT_OPERATIONS_IPP)
diff --git a/utils/text/operations_test.cpp b/utils/text/operations_test.cpp
new file mode 100644
index 000000000000..2d5ab36c9090
--- /dev/null
+++ b/utils/text/operations_test.cpp
@@ -0,0 +1,435 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/operations.ipp"
+
+#include <iostream>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Tests text::refill() on an input string with a range of widths.
+///
+/// \param expected The expected refilled paragraph.
+/// \param input The input paragraph to be refilled.
+/// \param first_width The first width to validate.
+/// \param last_width The last width to validate (inclusive).
+static void
+refill_test(const char* expected, const char* input,
+ const std::size_t first_width, const std::size_t last_width)
+{
+ for (std::size_t width = first_width; width <= last_width; ++width) {
+ const std::vector< std::string > lines = text::split(expected, '\n');
+ std::cout << "Breaking at width " << width << '\n';
+ ATF_REQUIRE_EQ(expected, text::refill_as_string(input, width));
+ ATF_REQUIRE(lines == text::refill(input, width));
+ }
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__empty);
+ATF_TEST_CASE_BODY(escape_xml__empty)
+{
+ ATF_REQUIRE_EQ("", text::escape_xml(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__no_escaping);
+ATF_TEST_CASE_BODY(escape_xml__no_escaping)
+{
+ ATF_REQUIRE_EQ("a", text::escape_xml("a"));
+ ATF_REQUIRE_EQ("Some text!", text::escape_xml("Some text!"));
+ ATF_REQUIRE_EQ("\n\t\r", text::escape_xml("\n\t\r"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__some_escaping);
+ATF_TEST_CASE_BODY(escape_xml__some_escaping)
+{
+ ATF_REQUIRE_EQ("&apos;", text::escape_xml("'"));
+
+ ATF_REQUIRE_EQ("foo &quot;bar&amp; &lt;tag&gt; yay&apos; baz",
+ text::escape_xml("foo \"bar& <tag> yay' baz"));
+
+ ATF_REQUIRE_EQ("&quot;&amp;&lt;&gt;&apos;", text::escape_xml("\"&<>'"));
+ ATF_REQUIRE_EQ("&amp;&amp;&amp;", text::escape_xml("&&&"));
+ ATF_REQUIRE_EQ("&amp;#8;&amp;#11;", text::escape_xml("\b\v"));
+ ATF_REQUIRE_EQ("\t&amp;#127;BAR&amp;", text::escape_xml("\t\x7f""BAR&"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__empty);
+ATF_TEST_CASE_BODY(quote__empty)
+{
+ ATF_REQUIRE_EQ("''", text::quote("", '\''));
+ ATF_REQUIRE_EQ("##", text::quote("", '#'));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__no_escaping);
+ATF_TEST_CASE_BODY(quote__no_escaping)
+{
+ ATF_REQUIRE_EQ("'Some text\"'", text::quote("Some text\"", '\''));
+ ATF_REQUIRE_EQ("#Another'string#", text::quote("Another'string", '#'));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__some_escaping);
+ATF_TEST_CASE_BODY(quote__some_escaping)
+{
+ ATF_REQUIRE_EQ("'Some\\'text'", text::quote("Some'text", '\''));
+ ATF_REQUIRE_EQ("#Some\\#text#", text::quote("Some#text", '#'));
+
+ ATF_REQUIRE_EQ("'More than one\\' quote\\''",
+ text::quote("More than one' quote'", '\''));
+ ATF_REQUIRE_EQ("'Multiple quotes \\'\\'\\' together'",
+ text::quote("Multiple quotes ''' together", '\''));
+
+ ATF_REQUIRE_EQ("'\\'escape at the beginning'",
+ text::quote("'escape at the beginning", '\''));
+ ATF_REQUIRE_EQ("'escape at the end\\''",
+ text::quote("escape at the end'", '\''));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__empty);
+ATF_TEST_CASE_BODY(refill__empty)
+{
+ ATF_REQUIRE_EQ(1, text::refill("", 0).size());
+ ATF_REQUIRE(text::refill("", 0)[0].empty());
+ ATF_REQUIRE_EQ("", text::refill_as_string("", 0));
+
+ ATF_REQUIRE_EQ(1, text::refill("", 10).size());
+ ATF_REQUIRE(text::refill("", 10)[0].empty());
+ ATF_REQUIRE_EQ("", text::refill_as_string("", 10));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__no_changes);
+ATF_TEST_CASE_BODY(refill__no_changes)
+{
+ std::vector< std::string > exp_lines;
+ exp_lines.push_back("foo bar\nbaz");
+
+ ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 12));
+ ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 12));
+
+ ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 18));
+ ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 80));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one);
+ATF_TEST_CASE_BODY(refill__break_one)
+{
+ refill_test("only break the\nfirst line", "only break the first line",
+ 14, 19);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one__not_first_word);
+ATF_TEST_CASE_BODY(refill__break_one__not_first_word)
+{
+ refill_test("first-long-word\nother\nwords", "first-long-word other words",
+ 6, 10);
+ refill_test("first-long-word\nother words", "first-long-word other words",
+ 11, 20);
+ refill_test("first-long-word other\nwords", "first-long-word other words",
+ 21, 26);
+ refill_test("first-long-word other words", "first-long-word other words",
+ 27, 28);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_many);
+ATF_TEST_CASE_BODY(refill__break_many)
+{
+ refill_test("this is a long\nparagraph to be\nsplit into\npieces",
+ "this is a long paragraph to be split into pieces",
+ 15, 15);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__cannot_break);
+ATF_TEST_CASE_BODY(refill__cannot_break)
+{
+ refill_test("this-is-a-long-string", "this-is-a-long-string", 5, 5);
+
+ refill_test("this is\na-string-with-long-words",
+ "this is a-string-with-long-words", 10, 10);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__preserve_whitespace);
+ATF_TEST_CASE_BODY(refill__preserve_whitespace)
+{
+ refill_test("foo bar baz ", "foo bar baz ", 80, 80);
+ refill_test("foo \n bar", "foo bar", 5, 5);
+
+ std::vector< std::string > exp_lines;
+ exp_lines.push_back("foo \n");
+ exp_lines.push_back(" bar");
+ ATF_REQUIRE(exp_lines == text::refill("foo \n bar", 5));
+ ATF_REQUIRE_EQ("foo \n\n bar", text::refill_as_string("foo \n bar", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__empty);
+ATF_TEST_CASE_BODY(join__empty)
+{
+ std::vector< std::string > lines;
+ ATF_REQUIRE_EQ("", text::join(lines, " "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__one);
+ATF_TEST_CASE_BODY(join__one)
+{
+ std::vector< std::string > lines;
+ lines.push_back("first line");
+ ATF_REQUIRE_EQ("first line", text::join(lines, "*"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__several);
+ATF_TEST_CASE_BODY(join__several)
+{
+ std::vector< std::string > lines;
+ lines.push_back("first abc");
+ lines.push_back("second");
+ lines.push_back("and last line");
+ ATF_REQUIRE_EQ("first abc second and last line", text::join(lines, " "));
+ ATF_REQUIRE_EQ("first abc***second***and last line",
+ text::join(lines, "***"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__unordered);
+ATF_TEST_CASE_BODY(join__unordered)
+{
+ std::set< std::string > lines;
+ lines.insert("first");
+ lines.insert("second");
+ const std::string joined = text::join(lines, " ");
+ ATF_REQUIRE(joined == "first second" || joined == "second first");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__empty);
+ATF_TEST_CASE_BODY(split__empty)
+{
+ std::vector< std::string > words = text::split("", ' ');
+ std::vector< std::string > exp_words;
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__one);
+ATF_TEST_CASE_BODY(split__one)
+{
+ std::vector< std::string > words = text::split("foo", ' ');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("foo");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__several__simple);
+ATF_TEST_CASE_BODY(split__several__simple)
+{
+ std::vector< std::string > words = text::split("foo bar baz", ' ');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("foo");
+ exp_words.push_back("bar");
+ exp_words.push_back("baz");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__several__delimiters);
+ATF_TEST_CASE_BODY(split__several__delimiters)
+{
+ std::vector< std::string > words = text::split("XfooXXbarXXXbazXX", 'X');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("");
+ exp_words.push_back("foo");
+ exp_words.push_back("");
+ exp_words.push_back("bar");
+ exp_words.push_back("");
+ exp_words.push_back("");
+ exp_words.push_back("baz");
+ exp_words.push_back("");
+ exp_words.push_back("");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__empty);
+ATF_TEST_CASE_BODY(replace_all__empty)
+{
+ ATF_REQUIRE_EQ("", text::replace_all("", "search", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__none);
+ATF_TEST_CASE_BODY(replace_all__none)
+{
+ ATF_REQUIRE_EQ("string without matches",
+ text::replace_all("string without matches",
+ "WITHOUT", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__one);
+ATF_TEST_CASE_BODY(replace_all__one)
+{
+ ATF_REQUIRE_EQ("string replacement matches",
+ text::replace_all("string without matches",
+ "without", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__several);
+ATF_TEST_CASE_BODY(replace_all__several)
+{
+ ATF_REQUIRE_EQ("OO fOO bar OOf baz OO",
+ text::replace_all("oo foo bar oof baz oo",
+ "oo", "OO"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__bool);
+ATF_TEST_CASE_BODY(to_type__ok__bool)
+{
+ ATF_REQUIRE( text::to_type< bool >("true"));
+ ATF_REQUIRE(!text::to_type< bool >("false"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__numerical);
+ATF_TEST_CASE_BODY(to_type__ok__numerical)
+{
+ ATF_REQUIRE_EQ(12, text::to_type< int >("12"));
+ ATF_REQUIRE_EQ(18745, text::to_type< int >("18745"));
+ ATF_REQUIRE_EQ(-12345, text::to_type< int >("-12345"));
+
+ ATF_REQUIRE_EQ(12.0, text::to_type< double >("12"));
+ ATF_REQUIRE_EQ(12.5, text::to_type< double >("12.5"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__string);
+ATF_TEST_CASE_BODY(to_type__ok__string)
+{
+ // While this seems redundant, having this particular specialization that
+ // does nothing allows callers to delegate work to to_type without worrying
+ // about the particular type being converted.
+ ATF_REQUIRE_EQ("", text::to_type< std::string >(""));
+ ATF_REQUIRE_EQ(" abcd ", text::to_type< std::string >(" abcd "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__empty);
+ATF_TEST_CASE_BODY(to_type__empty)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__bool);
+ATF_TEST_CASE_BODY(to_type__invalid__bool)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >(""));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("true "));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__numerical);
+ATF_TEST_CASE_BODY(to_type__invalid__numerical)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(" 3"));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3 "));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3a"));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("a3"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, escape_xml__empty);
+ ATF_ADD_TEST_CASE(tcs, escape_xml__no_escaping);
+ ATF_ADD_TEST_CASE(tcs, escape_xml__some_escaping);
+
+ ATF_ADD_TEST_CASE(tcs, quote__empty);
+ ATF_ADD_TEST_CASE(tcs, quote__no_escaping);
+ ATF_ADD_TEST_CASE(tcs, quote__some_escaping);
+
+ ATF_ADD_TEST_CASE(tcs, refill__empty);
+ ATF_ADD_TEST_CASE(tcs, refill__no_changes);
+ ATF_ADD_TEST_CASE(tcs, refill__break_one);
+ ATF_ADD_TEST_CASE(tcs, refill__break_one__not_first_word);
+ ATF_ADD_TEST_CASE(tcs, refill__break_many);
+ ATF_ADD_TEST_CASE(tcs, refill__cannot_break);
+ ATF_ADD_TEST_CASE(tcs, refill__preserve_whitespace);
+
+ ATF_ADD_TEST_CASE(tcs, join__empty);
+ ATF_ADD_TEST_CASE(tcs, join__one);
+ ATF_ADD_TEST_CASE(tcs, join__several);
+ ATF_ADD_TEST_CASE(tcs, join__unordered);
+
+ ATF_ADD_TEST_CASE(tcs, split__empty);
+ ATF_ADD_TEST_CASE(tcs, split__one);
+ ATF_ADD_TEST_CASE(tcs, split__several__simple);
+ ATF_ADD_TEST_CASE(tcs, split__several__delimiters);
+
+ ATF_ADD_TEST_CASE(tcs, replace_all__empty);
+ ATF_ADD_TEST_CASE(tcs, replace_all__none);
+ ATF_ADD_TEST_CASE(tcs, replace_all__one);
+ ATF_ADD_TEST_CASE(tcs, replace_all__several);
+
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__bool);
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__numerical);
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__string);
+ ATF_ADD_TEST_CASE(tcs, to_type__empty);
+ ATF_ADD_TEST_CASE(tcs, to_type__invalid__bool);
+ ATF_ADD_TEST_CASE(tcs, to_type__invalid__numerical);
+}
diff --git a/utils/text/regex.cpp b/utils/text/regex.cpp
new file mode 100644
index 000000000000..b078ba88f6b4
--- /dev/null
+++ b/utils/text/regex.cpp
@@ -0,0 +1,302 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/regex.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <regex.h>
+}
+
+#include "utils/auto_array.ipp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+static void throw_regex_error(const int, const ::regex_t*, const std::string&)
+ UTILS_NORETURN;
+
+
+/// Constructs and raises a regex_error.
+///
+/// \param error The error code returned by regcomp(3) or regexec(3).
+/// \param preg The native regex object that caused this error.
+/// \param prefix Error message prefix string.
+///
+/// \throw regex_error The constructed exception.
+static void
+throw_regex_error(const int error, const ::regex_t* preg,
+ const std::string& prefix)
+{
+ char buffer[1024];
+
+ // TODO(jmmv): Would be nice to handle the case where the message does
+ // not fit in the temporary buffer.
+ (void)::regerror(error, preg, buffer, sizeof(buffer));
+
+ throw text::regex_error(F("%s: %s") % prefix % buffer);
+}
+
+
+} // anonymous namespace
+
+
+/// Internal implementation for regex_matches.
+struct utils::text::regex_matches::impl : utils::noncopyable {
+ /// String on which we are matching.
+ ///
+ /// In theory, we could take a reference here instead of a copy, and make
+ /// it a requirement for the caller to ensure that the lifecycle of the
+ /// input string outlasts the lifecycle of the regex_matches. However, that
+ /// contract is very easy to break with hardcoded strings (as we do in
+ /// tests). Just go for the safer case here.
+ const std::string _string;
+
+ /// Maximum number of matching groups we expect, including the full match.
+ ///
+ /// In other words, this is the size of the _matches array.
+ const std::size_t _nmatches;
+
+ /// Native regular expression match representation.
+ utils::auto_array< ::regmatch_t > _matches;
+
+ /// Constructor.
+ ///
+ /// This executes the regex on the given string and sets up the internal
+ /// class state based on the results.
+ ///
+ /// \param preg The native regex object.
+ /// \param str The string on which to execute the regex.
+ /// \param ngroups Number of capture groups in the regex. This is an upper
+ /// bound and may be greater than the actual matches.
+ ///
+ /// \throw regex_error If the call to regexec(3) fails.
+ impl(const ::regex_t* preg, const std::string& str,
+ const std::size_t ngroups) :
+ _string(str),
+ _nmatches(ngroups + 1),
+ _matches(new ::regmatch_t[_nmatches])
+ {
+ const int error = ::regexec(preg, _string.c_str(), _nmatches,
+ _matches.get(), 0);
+ if (error == REG_NOMATCH) {
+ _matches.reset(NULL);
+ } else if (error != 0) {
+ throw_regex_error(error, preg,
+ F("regexec on '%s' failed") % _string);
+ }
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed implementation of the object.
+text::regex_matches::regex_matches(std::shared_ptr< impl > pimpl) :
+ _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+text::regex_matches::~regex_matches(void)
+{
+}
+
+
+/// Returns the number of matches in this object.
+///
+/// Note that this does not correspond to the number of groups provided at
+/// construction time. The returned value here accounts for only the returned
+/// valid matches.
+///
+/// \return Number of matches, including the full match.
+std::size_t
+text::regex_matches::count(void) const
+{
+ std::size_t total = 0;
+ if (_pimpl->_matches.get() != NULL) {
+ for (std::size_t i = 0; i < _pimpl->_nmatches; ++i) {
+ if (_pimpl->_matches[i].rm_so != -1)
+ ++total;
+ }
+ INV(total <= _pimpl->_nmatches);
+ }
+ return total;
+}
+
+
+/// Gets a match.
+///
+/// \param index Number of the match to get. Index 0 always contains the match
+/// of the whole regex.
+///
+/// \pre There regex must have matched the input string.
+/// \pre index must be lower than count().
+///
+/// \return The textual match.
+std::string
+text::regex_matches::get(const std::size_t index) const
+{
+ PRE(*this);
+ PRE(index < count());
+
+ const ::regmatch_t* match = &_pimpl->_matches[index];
+
+ return std::string(_pimpl->_string.c_str() + match->rm_so,
+ match->rm_eo - match->rm_so);
+}
+
+
+/// Checks if there are any matches.
+///
+/// \return True if the object contains one or more matches; false otherwise.
+text::regex_matches::operator bool(void) const
+{
+ return _pimpl->_matches.get() != NULL;
+}
+
+
+/// Internal implementation for regex.
+struct utils::text::regex::impl : utils::noncopyable {
+ /// Native regular expression representation.
+ ::regex_t _preg;
+
+ /// Number of capture groups in the regular expression. This is an upper
+ /// bound and does NOT include the default full string match.
+ std::size_t _ngroups;
+
+ /// Constructor.
+ ///
+ /// This compiles the given regular expression.
+ ///
+ /// \param regex_ The regular expression to compile.
+ /// \param ngroups Number of capture groups in the regular expression. This
+ /// is an upper bound and does NOT include the default full string
+ /// match.
+ /// \param ignore_case Whether to ignore case during matching.
+ ///
+ /// \throw regex_error If the call to regcomp(3) fails.
+ impl(const std::string& regex_, const std::size_t ngroups,
+ const bool ignore_case) :
+ _ngroups(ngroups)
+ {
+ const int flags = REG_EXTENDED | (ignore_case ? REG_ICASE : 0);
+ const int error = ::regcomp(&_preg, regex_.c_str(), flags);
+ if (error != 0)
+ throw_regex_error(error, &_preg, F("regcomp on '%s' failed")
+ % regex_);
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ ::regfree(&_preg);
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed implementation of the object.
+text::regex::regex(std::shared_ptr< impl > pimpl) : _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+text::regex::~regex(void)
+{
+}
+
+
+/// Compiles a new regular expression.
+///
+/// \param regex_ The regular expression to compile.
+/// \param ngroups Number of capture groups in the regular expression. This is
+/// an upper bound and does NOT include the default full string match.
+/// \param ignore_case Whether to ignore case during matching.
+///
+/// \return A new regular expression, ready to match strings.
+///
+/// \throw regex_error If the regular expression is invalid and cannot be
+/// compiled.
+text::regex
+text::regex::compile(const std::string& regex_, const std::size_t ngroups,
+ const bool ignore_case)
+{
+ return regex(std::shared_ptr< impl >(new impl(regex_, ngroups,
+ ignore_case)));
+}
+
+
+/// Matches the regular expression against a string.
+///
+/// \param str String to match the regular expression against.
+///
+/// \return A new regex_matches object with the results of the match.
+text::regex_matches
+text::regex::match(const std::string& str) const
+{
+ std::shared_ptr< regex_matches::impl > pimpl(new regex_matches::impl(
+ &_pimpl->_preg, str, _pimpl->_ngroups));
+ return regex_matches(pimpl);
+}
+
+
+/// Compiles and matches a regular expression once.
+///
+/// This is syntactic sugar to simplify the instantiation of a new regex object
+/// and its subsequent match on a string.
+///
+/// \param regex_ The regular expression to compile and match.
+/// \param str String to match the regular expression against.
+/// \param ngroups Number of capture groups in the regular expression.
+/// \param ignore_case Whether to ignore case during matching.
+///
+/// \return A new regex_matches object with the results of the match.
+text::regex_matches
+text::match_regex(const std::string& regex_, const std::string& str,
+ const std::size_t ngroups, const bool ignore_case)
+{
+ return regex::compile(regex_, ngroups, ignore_case).match(str);
+}
diff --git a/utils/text/regex.hpp b/utils/text/regex.hpp
new file mode 100644
index 000000000000..b3d20c246735
--- /dev/null
+++ b/utils/text/regex.hpp
@@ -0,0 +1,92 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/regex.hpp
+/// Utilities to build and match regular expressions.
+
+#if !defined(UTILS_TEXT_REGEX_HPP)
+#define UTILS_TEXT_REGEX_HPP
+
+#include "utils/text/regex_fwd.hpp"
+
+#include <cstddef>
+#include <memory>
+
+
+namespace utils {
+namespace text {
+
+
+/// Container for regex match results.
+class regex_matches {
+ struct impl;
+
+ /// Pointer to shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class regex;
+ regex_matches(std::shared_ptr< impl >);
+
+public:
+ ~regex_matches(void);
+
+ std::size_t count(void) const;
+ std::string get(const std::size_t) const;
+
+ operator bool(void) const;
+};
+
+
+/// Regular expression compiler and executor.
+///
+/// All regular expressions handled by this class are "extended".
+class regex {
+ struct impl;
+
+ /// Pointer to shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ regex(std::shared_ptr< impl >);
+
+public:
+ ~regex(void);
+
+ static regex compile(const std::string&, const std::size_t,
+ const bool = false);
+ regex_matches match(const std::string&) const;
+};
+
+
+regex_matches match_regex(const std::string&, const std::string&,
+ const std::size_t, const bool = false);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_REGEX_HPP)
diff --git a/utils/text/regex_fwd.hpp b/utils/text/regex_fwd.hpp
new file mode 100644
index 000000000000..e9010324c10d
--- /dev/null
+++ b/utils/text/regex_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/regex_fwd.hpp
+/// Forward declarations for utils/text/regex.hpp
+
+#if !defined(UTILS_TEXT_REGEX_FWD_HPP)
+#define UTILS_TEXT_REGEX_FWD_HPP
+
+namespace utils {
+namespace text {
+
+
+class regex_matches;
+class regex;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_REGEX_FWD_HPP)
diff --git a/utils/text/regex_test.cpp b/utils/text/regex_test.cpp
new file mode 100644
index 000000000000..7ea5ee485aad
--- /dev/null
+++ b/utils/text/regex_test.cpp
@@ -0,0 +1,177 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/regex.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__no_matches);
+ATF_TEST_CASE_BODY(integration__no_matches)
+{
+ const text::regex_matches matches = text::match_regex(
+ "foo.*bar", "this is a string without the searched text", 0);
+ ATF_REQUIRE(!matches);
+ ATF_REQUIRE_EQ(0, matches.count());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__no_capture_groups);
+ATF_TEST_CASE_BODY(integration__no_capture_groups)
+{
+ const text::regex_matches matches = text::match_regex(
+ "foo.*bar", "this is a string with foo and bar embedded in it", 0);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(1, matches.count());
+ ATF_REQUIRE_EQ("foo and bar", matches.get(0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__one_capture_group);
+ATF_TEST_CASE_BODY(integration__one_capture_group)
+{
+ const text::regex_matches matches = text::match_regex(
+ "^([^ ]*) ", "the string", 1);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("the ", matches.get(0));
+ ATF_REQUIRE_EQ("the", matches.get(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__many_capture_groups);
+ATF_TEST_CASE_BODY(integration__many_capture_groups)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 2);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(3, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+ ATF_REQUIRE_EQ("string", matches.get(2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__capture_groups_underspecified);
+ATF_TEST_CASE_BODY(integration__capture_groups_underspecified)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 1);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__capture_groups_overspecified);
+ATF_TEST_CASE_BODY(integration__capture_groups_overspecified)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 10);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(3, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+ ATF_REQUIRE_EQ("string", matches.get(2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__reuse_regex_in_multiple_matches);
+ATF_TEST_CASE_BODY(integration__reuse_regex_in_multiple_matches)
+{
+ const text::regex regex = text::regex::compile("number is ([0-9]+)", 1);
+
+ {
+ const text::regex_matches matches = regex.match("my number is 581.");
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("number is 581", matches.get(0));
+ ATF_REQUIRE_EQ("581", matches.get(1));
+ }
+
+ {
+ const text::regex_matches matches = regex.match("your number is 6");
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("number is 6", matches.get(0));
+ ATF_REQUIRE_EQ("6", matches.get(1));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__ignore_case);
+ATF_TEST_CASE_BODY(integration__ignore_case)
+{
+ const text::regex regex1 = text::regex::compile("foo", 0, false);
+ ATF_REQUIRE(!regex1.match("bar Foo bar"));
+ ATF_REQUIRE(!regex1.match("bar foO bar"));
+ ATF_REQUIRE(!regex1.match("bar FOO bar"));
+
+ ATF_REQUIRE(!text::match_regex("foo", "bar Foo bar", 0, false));
+ ATF_REQUIRE(!text::match_regex("foo", "bar foO bar", 0, false));
+ ATF_REQUIRE(!text::match_regex("foo", "bar FOO bar", 0, false));
+
+ const text::regex regex2 = text::regex::compile("foo", 0, true);
+ ATF_REQUIRE( regex2.match("bar foo bar"));
+ ATF_REQUIRE( regex2.match("bar Foo bar"));
+ ATF_REQUIRE( regex2.match("bar foO bar"));
+ ATF_REQUIRE( regex2.match("bar FOO bar"));
+
+ ATF_REQUIRE( text::match_regex("foo", "bar foo bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar Foo bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar foO bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar FOO bar", 0, true));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__invalid_regex);
+ATF_TEST_CASE_BODY(integration__invalid_regex)
+{
+ ATF_REQUIRE_THROW(text::regex_error,
+ text::regex::compile("this is (unbalanced", 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // regex and regex_matches are so coupled that it makes no sense to test
+ // them independently. Just validate their integration.
+ ATF_ADD_TEST_CASE(tcs, integration__no_matches);
+ ATF_ADD_TEST_CASE(tcs, integration__no_capture_groups);
+ ATF_ADD_TEST_CASE(tcs, integration__one_capture_group);
+ ATF_ADD_TEST_CASE(tcs, integration__many_capture_groups);
+ ATF_ADD_TEST_CASE(tcs, integration__capture_groups_underspecified);
+ ATF_ADD_TEST_CASE(tcs, integration__capture_groups_overspecified);
+ ATF_ADD_TEST_CASE(tcs, integration__reuse_regex_in_multiple_matches);
+ ATF_ADD_TEST_CASE(tcs, integration__ignore_case);
+ ATF_ADD_TEST_CASE(tcs, integration__invalid_regex);
+}
diff --git a/utils/text/table.cpp b/utils/text/table.cpp
new file mode 100644
index 000000000000..4a2c72f8053f
--- /dev/null
+++ b/utils/text/table.cpp
@@ -0,0 +1,428 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/table.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <sstream>
+
+#include "utils/sanity.hpp"
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Applies user overrides to the column widths of a table.
+///
+/// \param table The table from which to calculate the column widths.
+/// \param user_widths The column widths provided by the user. This vector must
+/// have less or the same number of elements as the columns of the table.
+/// Values of width_auto are ignored; any other explicit values are copied
+/// to the output widths vector, including width_refill.
+///
+/// \return A vector with the widths of the columns of the input table with any
+/// user overrides applied.
+static text::widths_vector
+override_column_widths(const text::table& table,
+ const text::widths_vector& user_widths)
+{
+ PRE(user_widths.size() <= table.ncolumns());
+ text::widths_vector widths = table.column_widths();
+
+ // Override the actual width of the columns based on user-specified widths.
+ for (text::widths_vector::size_type i = 0; i < user_widths.size(); ++i) {
+ const text::widths_vector::value_type& user_width = user_widths[i];
+ if (user_width != text::table_formatter::width_auto) {
+ PRE_MSG(user_width == text::table_formatter::width_refill ||
+ user_width >= widths[i],
+ "User-provided column widths must be larger than the "
+ "column contents (except for the width_refill column)");
+ widths[i] = user_width;
+ }
+ }
+
+ return widths;
+}
+
+
+/// Locates the refill column, if any.
+///
+/// \param widths The widths of the columns as returned by
+/// override_column_widths(). Note that one of the columns may or may not
+/// be width_refill, which is the column we are looking for.
+///
+/// \return The index of the refill column with a width_refill width if any, or
+/// otherwise the index of the last column (which is the default refill column).
+static text::widths_vector::size_type
+find_refill_column(const text::widths_vector& widths)
+{
+ text::widths_vector::size_type i = 0;
+ for (; i < widths.size(); ++i) {
+ if (widths[i] == text::table_formatter::width_refill)
+ return i;
+ }
+ return i - 1;
+}
+
+
+/// Pads the widths of the table to fit within a maximum width.
+///
+/// On output, a column of the widths vector is truncated to a shorter length
+/// than its current value, if the total width of the table would exceed the
+/// maximum table width.
+///
+/// \param [in,out] widths The widths of the columns as returned by
+/// override_column_widths(). One of these columns should have a value of
+/// width_refill; if not, a default column is refilled.
+/// \param user_max_width The target width of the table; must not be zero.
+/// \param column_padding The padding between the cells, if any. The target
+/// width should be larger than the padding times the number of columns; if
+/// that is not the case, we attempt a readjustment here.
+static void
+refill_widths(text::widths_vector& widths,
+ const text::widths_vector::value_type user_max_width,
+ const std::size_t column_padding)
+{
+ PRE(user_max_width != 0);
+
+ // widths.size() is a proxy for the number of columns of the table.
+ const std::size_t total_padding = column_padding * (widths.size() - 1);
+ const text::widths_vector::value_type max_width = std::max(
+ user_max_width, total_padding) - total_padding;
+
+ const text::widths_vector::size_type refill_column =
+ find_refill_column(widths);
+ INV(refill_column < widths.size());
+
+ text::widths_vector::value_type width = 0;
+ for (text::widths_vector::size_type i = 0; i < widths.size(); ++i) {
+ if (i != refill_column)
+ width += widths[i];
+ }
+ widths[refill_column] = max_width - width;
+}
+
+
+/// Pads an input text to a specified width with spaces.
+///
+/// \param input The text to add padding to (may be empty).
+/// \param length The desired length of the output.
+/// \param is_last Whether the text being processed belongs to the last column
+/// of a row or not. Values in the last column should not be padded to
+/// prevent trailing whitespace on the screen (which affects copy/pasting
+/// for example).
+///
+/// \return The padded cell. If the input string is longer than the desired
+/// length, the input string is returned verbatim. The padded table won't be
+/// correct, but we don't expect this to be a common case to worry about.
+static std::string
+pad_cell(const std::string& input, const std::size_t length, const bool is_last)
+{
+ if (is_last)
+ return input;
+ else {
+ if (input.length() < length)
+ return input + std::string(length - input.length(), ' ');
+ else
+ return input;
+ }
+}
+
+
+/// Refills a cell and adds it to the output lines.
+///
+/// \param row The row containing the cell to be refilled.
+/// \param widths The widths of the row.
+/// \param column The column being refilled.
+/// \param [in,out] textual_rows The output lines as processed so far. This is
+/// updated to accomodate for the contents of the refilled cell, extending
+/// the rows as necessary.
+static void
+refill_cell(const text::table_row& row, const text::widths_vector& widths,
+ const text::table_row::size_type column,
+ std::vector< text::table_row >& textual_rows)
+{
+ const std::vector< std::string > rows = text::refill(row[column],
+ widths[column]);
+
+ if (textual_rows.size() < rows.size())
+ textual_rows.resize(rows.size(), text::table_row(row.size()));
+
+ for (std::vector< std::string >::size_type i = 0; i < rows.size(); ++i) {
+ for (text::table_row::size_type j = 0; j < row.size(); ++j) {
+ const bool is_last = j == row.size() - 1;
+ if (j == column)
+ textual_rows[i][j] = pad_cell(rows[i], widths[j], is_last);
+ else {
+ if (textual_rows[i][j].empty())
+ textual_rows[i][j] = pad_cell("", widths[j], is_last);
+ }
+ }
+ }
+}
+
+
+/// Formats a single table row.
+///
+/// \param row The row to format.
+/// \param widths The widths of the columns to apply during formatting. Cells
+/// wider than the specified width are refilled to attempt to fit in the
+/// cell. Cells narrower than the width are right-padded with spaces.
+/// \param separator The column separator to use.
+///
+/// \return The textual lines that contain the formatted row.
+static std::vector< std::string >
+format_row(const text::table_row& row, const text::widths_vector& widths,
+ const std::string& separator)
+{
+ PRE(row.size() == widths.size());
+
+ std::vector< text::table_row > textual_rows(1, text::table_row(row.size()));
+
+ for (text::table_row::size_type column = 0; column < row.size(); ++column) {
+ if (widths[column] > row[column].length())
+ textual_rows[0][column] = pad_cell(row[column], widths[column],
+ column == row.size() - 1);
+ else
+ refill_cell(row, widths, column, textual_rows);
+ }
+
+ std::vector< std::string > lines;
+ for (std::vector< text::table_row >::const_iterator
+ iter = textual_rows.begin(); iter != textual_rows.end(); ++iter) {
+ lines.push_back(text::join(*iter, separator));
+ }
+ return lines;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new table.
+///
+/// \param ncolumns_ The number of columns that the table will have.
+text::table::table(const table_row::size_type ncolumns_)
+{
+ _column_widths.resize(ncolumns_, 0);
+}
+
+
+/// Gets the number of columns in the table.
+///
+/// \return The number of columns in the table. This value remains constant
+/// during the existence of the table.
+text::widths_vector::size_type
+text::table::ncolumns(void) const
+{
+ return _column_widths.size();
+}
+
+
+/// Gets the width of a column.
+///
+/// The returned value is not valid if add_row() is called again, as the column
+/// may have grown in width.
+///
+/// \param column The index of the column of which to get the width. Must be
+/// less than the total number of columns.
+///
+/// \return The width of a column.
+text::widths_vector::value_type
+text::table::column_width(const widths_vector::size_type column) const
+{
+ PRE(column < _column_widths.size());
+ return _column_widths[column];
+}
+
+
+/// Gets the widths of all columns.
+///
+/// The returned value is not valid if add_row() is called again, as the columns
+/// may have grown in width.
+///
+/// \return A vector with the width of all columns.
+const text::widths_vector&
+text::table::column_widths(void) const
+{
+ return _column_widths;
+}
+
+
+/// Checks whether the table is empty or not.
+///
+/// \return True if the table is empty; false otherwise.
+bool
+text::table::empty(void) const
+{
+ return _rows.empty();
+}
+
+
+/// Adds a row to the table.
+///
+/// \param row The row to be added. This row must have the same amount of
+/// columns as defined during the construction of the table.
+void
+text::table::add_row(const table_row& row)
+{
+ PRE(row.size() == _column_widths.size());
+ _rows.push_back(row);
+
+ for (table_row::size_type i = 0; i < row.size(); ++i)
+ if (_column_widths[i] < row[i].length())
+ _column_widths[i] = row[i].length();
+}
+
+
+/// Gets an iterator pointing to the beginning of the rows of the table.
+///
+/// \return An iterator on the rows.
+text::table::const_iterator
+text::table::begin(void) const
+{
+ return _rows.begin();
+}
+
+
+/// Gets an iterator pointing to the end of the rows of the table.
+///
+/// \return An iterator on the rows.
+text::table::const_iterator
+text::table::end(void) const
+{
+ return _rows.end();
+}
+
+
+/// Column width to denote that the column has to fit all of its cells.
+const std::size_t text::table_formatter::width_auto = 0;
+
+
+/// Column width to denote that the column can be refilled to fit the table.
+const std::size_t text::table_formatter::width_refill =
+ std::numeric_limits< std::size_t >::max();
+
+
+/// Constructs a new table formatter.
+text::table_formatter::table_formatter(void) :
+ _separator(""),
+ _table_width(0)
+{
+}
+
+
+/// Sets the width of a column.
+///
+/// All columns except one must have a width that is, at least, as wide as the
+/// widest cell in the column. One of the columns can have a width of
+/// width_refill, which indicates that the column will be refilled if the table
+/// does not fit in its maximum width.
+///
+/// \param column The index of the column to set the width for.
+/// \param width The width to set the column to.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_column_width(const table_row::size_type column,
+ const std::size_t width)
+{
+#if !defined(NDEBUG)
+ if (width == width_refill) {
+ for (widths_vector::size_type i = 0; i < _column_widths.size(); i++) {
+ if (i != column)
+ PRE_MSG(_column_widths[i] != width_refill,
+ "Only one column width can be set to width_refill");
+ }
+ }
+#endif
+
+ if (_column_widths.size() < column + 1)
+ _column_widths.resize(column + 1, width_auto);
+ _column_widths[column] = width;
+ return *this;
+}
+
+
+/// Sets the separator to use between the cells.
+///
+/// \param separator The separator to use.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_separator(const char* separator)
+{
+ _separator = separator;
+ return *this;
+}
+
+
+/// Sets the maximum width of the table.
+///
+/// \param table_width The maximum width of the table; cannot be zero.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_table_width(const std::size_t table_width)
+{
+ PRE(table_width > 0);
+ _table_width = table_width;
+ return *this;
+}
+
+
+/// Formats a table into a collection of textual lines.
+///
+/// \param t Table to format.
+///
+/// \return A collection of textual lines.
+std::vector< std::string >
+text::table_formatter::format(const table& t) const
+{
+ std::vector< std::string > lines;
+
+ if (!t.empty()) {
+ widths_vector widths = override_column_widths(t, _column_widths);
+ if (_table_width != 0)
+ refill_widths(widths, _table_width, _separator.length());
+
+ for (table::const_iterator iter = t.begin(); iter != t.end(); ++iter) {
+ const std::vector< std::string > sublines =
+ format_row(*iter, widths, _separator);
+ std::copy(sublines.begin(), sublines.end(),
+ std::back_inserter(lines));
+ }
+ }
+
+ return lines;
+}
diff --git a/utils/text/table.hpp b/utils/text/table.hpp
new file mode 100644
index 000000000000..5fd7c50c991c
--- /dev/null
+++ b/utils/text/table.hpp
@@ -0,0 +1,125 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/table.hpp
+/// Table construction and formatting.
+
+#if !defined(UTILS_TEXT_TABLE_HPP)
+#define UTILS_TEXT_TABLE_HPP
+
+#include "utils/text/table_fwd.hpp"
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+/// Representation of a table.
+///
+/// A table is nothing more than a matrix of rows by columns. The number of
+/// columns is hardcoded at construction times, and the rows can be accumulated
+/// at a later stage.
+///
+/// The only value of this class is a simpler and more natural mechanism of the
+/// construction of a table, with additional sanity checks. We could as well
+/// just expose the internal data representation to our users.
+class table {
+ /// Widths of the table columns so far.
+ widths_vector _column_widths;
+
+ /// Type defining the collection of rows in the table.
+ typedef std::vector< table_row > rows_vector;
+
+ /// The rows of the table.
+ ///
+ /// This is actually the matrix representing the table. Every element of
+ /// this vector (which are vectors themselves) must have _ncolumns items.
+ rows_vector _rows;
+
+public:
+ table(const table_row::size_type);
+
+ widths_vector::size_type ncolumns(void) const;
+ widths_vector::value_type column_width(const widths_vector::size_type)
+ const;
+ const widths_vector& column_widths(void) const;
+
+ void add_row(const table_row&);
+
+ bool empty(void) const;
+
+ /// Constant iterator on the rows of the table.
+ typedef rows_vector::const_iterator const_iterator;
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+};
+
+
+/// Settings to format a table.
+///
+/// This class implements a builder pattern to construct an object that contains
+/// all the knowledge to format a table. Once all the settings have been set,
+/// the format() method provides the algorithm to apply such formatting settings
+/// to any input table.
+class table_formatter {
+ /// Text to use as the separator between cells.
+ std::string _separator;
+
+ /// Colletion of widths of the columns of a table.
+ std::size_t _table_width;
+
+ /// Widths of the table columns.
+ ///
+ /// Note that this only includes widths for the column widths explicitly
+ /// overriden by the caller. In other words, this vector can be shorter
+ /// than the table passed to the format() method, which is just fine. Any
+ /// non-specified column widths are assumed to be width_auto.
+ widths_vector _column_widths;
+
+public:
+ table_formatter(void);
+
+ static const std::size_t width_auto;
+ static const std::size_t width_refill;
+ table_formatter& set_column_width(const table_row::size_type,
+ const std::size_t);
+ table_formatter& set_separator(const char*);
+ table_formatter& set_table_width(const std::size_t);
+
+ std::vector< std::string > format(const table&) const;
+};
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TABLE_HPP)
diff --git a/utils/text/table_fwd.hpp b/utils/text/table_fwd.hpp
new file mode 100644
index 000000000000..77c6b1fa8c78
--- /dev/null
+++ b/utils/text/table_fwd.hpp
@@ -0,0 +1,58 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/table_fwd.hpp
+/// Forward declarations for utils/text/table.hpp
+
+#if !defined(UTILS_TEXT_TABLE_FWD_HPP)
+#define UTILS_TEXT_TABLE_FWD_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+/// Values of the cells of a particular table row.
+typedef std::vector< std::string > table_row;
+
+
+/// Vector of column widths.
+typedef std::vector< std::size_t > widths_vector;
+
+
+class table;
+class table_formatter;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TABLE_FWD_HPP)
diff --git a/utils/text/table_test.cpp b/utils/text/table_test.cpp
new file mode 100644
index 000000000000..45928dae89c4
--- /dev/null
+++ b/utils/text/table_test.cpp
@@ -0,0 +1,413 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/table.hpp"
+
+#include <algorithm>
+
+#include <atf-c++.hpp>
+
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+/// Performs a check on text::table_formatter.
+///
+/// This is provided for test simplicity's sake. Having to match the result of
+/// the formatting on a line by line basis would result in too verbose tests
+/// (maybe not with C++11, but not using this yet).
+///
+/// Because of the flattening of the formatted table into a string, we risk
+/// misdetecting problems when the algorithm bundles newlines into the lines of
+/// a table. This should not happen, and not accounting for this little detail
+/// makes testing so much easier.
+///
+/// \param expected Textual representation of the table, as a collection of
+/// lines separated by newline characters.
+/// \param formatter The formatter to use.
+/// \param table The table to format.
+static void
+table_formatter_check(const std::string& expected,
+ const text::table_formatter& formatter,
+ const text::table& table)
+{
+ ATF_REQUIRE_EQ(expected, text::join(formatter.format(table), "\n") + "\n");
+}
+
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__ncolumns);
+ATF_TEST_CASE_BODY(table__ncolumns)
+{
+ ATF_REQUIRE_EQ(5, text::table(5).ncolumns());
+ ATF_REQUIRE_EQ(10, text::table(10).ncolumns());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__column_width);
+ATF_TEST_CASE_BODY(table__column_width)
+{
+ text::table_row row1;
+ row1.push_back("1234");
+ row1.push_back("123456");
+ text::table_row row2;
+ row2.push_back("12");
+ row2.push_back("12345678");
+
+ text::table table(2);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ ATF_REQUIRE_EQ(4, table.column_width(0));
+ ATF_REQUIRE_EQ(8, table.column_width(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__column_widths);
+ATF_TEST_CASE_BODY(table__column_widths)
+{
+ text::table_row row1;
+ row1.push_back("1234");
+ row1.push_back("123456");
+ text::table_row row2;
+ row2.push_back("12");
+ row2.push_back("12345678");
+
+ text::table table(2);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ ATF_REQUIRE_EQ(4, table.column_widths()[0]);
+ ATF_REQUIRE_EQ(8, table.column_widths()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__empty);
+ATF_TEST_CASE_BODY(table__empty)
+{
+ text::table table(2);
+ ATF_REQUIRE(table.empty());
+ table.add_row(text::table_row(2));
+ ATF_REQUIRE(!table.empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__iterate);
+ATF_TEST_CASE_BODY(table__iterate)
+{
+ text::table_row row1;
+ row1.push_back("foo");
+ text::table_row row2;
+ row2.push_back("bar");
+
+ text::table table(1);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ text::table::const_iterator iter = table.begin();
+ ATF_REQUIRE(iter != table.end());
+ ATF_REQUIRE(row1 == *iter);
+ ++iter;
+ ATF_REQUIRE(iter != table.end());
+ ATF_REQUIRE(row2 == *iter);
+ ++iter;
+ ATF_REQUIRE(iter == table.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__empty);
+ATF_TEST_CASE_BODY(table_formatter__empty)
+{
+ ATF_REQUIRE(text::table_formatter().set_separator(" ")
+ .format(text::table(1)).empty());
+ ATF_REQUIRE(text::table_formatter().set_separator(" ")
+ .format(text::table(10)).empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__defaults);
+ATF_TEST_CASE_BODY(table_formatter__defaults)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First Second Third\n"
+ "Fourth with some textFifth with some more textSixth foo\n",
+ text::table_formatter(), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__no_max_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__no_max_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row with some words\n"
+ "Second row with some words\n",
+ text::table_formatter().set_separator(" | "), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__explicit_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__explicit_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row with some words\n"
+ "Second row with some words\n",
+ text::table_formatter().set_separator(" | ").set_column_width(0, 1024),
+ table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__max_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__max_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row\nwith some\nwords\n"
+ "Second row\nwith some\nwords\n",
+ text::table_formatter().set_separator(" | ").set_table_width(11),
+ table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__no_max_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__no_max_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with some more text | Sixth foo\n",
+ text::table_formatter().set_separator(" | "), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__explicit_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__explicit_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with some more text | Sixth foo\n",
+ text::table_formatter().set_separator(" | ").set_column_width(0, 23)
+ .set_column_width(1, 28), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__max_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__max_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with | Sixth foo\n"
+ " | some more | \n"
+ " | text | \n",
+ text::table_formatter().set_separator(" | ").set_table_width(46)
+ .set_column_width(1, text::table_formatter::width_refill)
+ .set_column_width(0, text::table_formatter::width_auto), table);
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with | Sixth foo\n"
+ " | some more | \n"
+ " | text | \n",
+ text::table_formatter().set_separator(" | ").set_table_width(48)
+ .set_column_width(1, text::table_formatter::width_refill)
+ .set_column_width(0, 23), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__use_case__cli_help);
+ATF_TEST_CASE_BODY(table_formatter__use_case__cli_help)
+{
+ text::table options_table(2);
+ {
+ text::table_row row;
+ row.push_back("-a a_value");
+ row.push_back("This is the description of the first flag");
+ options_table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("-b");
+ row.push_back("And this is the text for the second flag");
+ options_table.add_row(row);
+ }
+
+ text::table commands_table(2);
+ {
+ text::table_row row;
+ row.push_back("first");
+ row.push_back("This is the first command");
+ commands_table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("second");
+ row.push_back("And this is the second command");
+ commands_table.add_row(row);
+ }
+
+ const text::widths_vector::value_type first_width =
+ std::max(options_table.column_width(0), commands_table.column_width(0));
+
+ table_formatter_check(
+ "-a a_value This is the description\n"
+ " of the first flag\n"
+ "-b And this is the text for\n"
+ " the second flag\n",
+ text::table_formatter().set_separator(" ").set_table_width(36)
+ .set_column_width(0, first_width)
+ .set_column_width(1, text::table_formatter::width_refill),
+ options_table);
+
+ table_formatter_check(
+ "first This is the first\n"
+ " command\n"
+ "second And this is the second\n"
+ " command\n",
+ text::table_formatter().set_separator(" ").set_table_width(36)
+ .set_column_width(0, first_width)
+ .set_column_width(1, text::table_formatter::width_refill),
+ commands_table);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, table__ncolumns);
+ ATF_ADD_TEST_CASE(tcs, table__column_width);
+ ATF_ADD_TEST_CASE(tcs, table__column_widths);
+ ATF_ADD_TEST_CASE(tcs, table__empty);
+ ATF_ADD_TEST_CASE(tcs, table__iterate);
+
+ ATF_ADD_TEST_CASE(tcs, table_formatter__empty);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__defaults);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__no_max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__explicit_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__no_max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__explicit_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__use_case__cli_help);
+}
diff --git a/utils/text/templates.cpp b/utils/text/templates.cpp
new file mode 100644
index 000000000000..13cb27b1cce2
--- /dev/null
+++ b/utils/text/templates.cpp
@@ -0,0 +1,764 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/templates.hpp"
+
+#include <algorithm>
+#include <fstream>
+#include <sstream>
+#include <stack>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Definition of a template statement.
+///
+/// A template statement is a particular line in the input file that is
+/// preceeded by a template marker. This class provides a high-level
+/// representation of the contents of such statement and a mechanism to parse
+/// the textual line into this high-level representation.
+class statement_def {
+public:
+ /// Types of the known statements.
+ enum statement_type {
+ /// Alternative clause of a conditional.
+ ///
+ /// Takes no arguments.
+ type_else,
+
+ /// End of conditional marker.
+ ///
+ /// Takes no arguments.
+ type_endif,
+
+ /// End of loop marker.
+ ///
+ /// Takes no arguments.
+ type_endloop,
+
+ /// Beginning of a conditional.
+ ///
+ /// Takes a single argument, which denotes the name of the variable or
+ /// vector to check for existence. This is the only expression
+ /// supported.
+ type_if,
+
+ /// Beginning of a loop over all the elements of a vector.
+ ///
+ /// Takes two arguments: the name of the vector over which to iterate
+ /// and the name of the iterator to later index this vector.
+ type_loop,
+ };
+
+private:
+ /// Internal data describing the structure of a particular statement type.
+ struct type_descriptor {
+ /// The native type of the statement.
+ statement_type type;
+
+ /// The expected number of arguments.
+ unsigned int n_arguments;
+
+ /// Constructs a new type descriptor.
+ ///
+ /// \param type_ The native type of the statement.
+ /// \param n_arguments_ The expected number of arguments.
+ type_descriptor(const statement_type type_,
+ const unsigned int n_arguments_)
+ : type(type_), n_arguments(n_arguments_)
+ {
+ }
+ };
+
+ /// Mapping of statement type names to their definitions.
+ typedef std::map< std::string, type_descriptor > types_map;
+
+ /// Description of the different statement types.
+ ///
+ /// This static map is initialized once and reused later for any statement
+ /// lookup. Unfortunately, we cannot perform this initialization in a
+ /// static manner without C++11.
+ static types_map _types;
+
+ /// Generates a new types definition map.
+ ///
+ /// \return A new types definition map, to be assigned to _types.
+ static types_map
+ generate_types_map(void)
+ {
+ // If you change this, please edit the comments in the enum above.
+ types_map types;
+ types.insert(types_map::value_type(
+ "else", type_descriptor(type_else, 0)));
+ types.insert(types_map::value_type(
+ "endif", type_descriptor(type_endif, 0)));
+ types.insert(types_map::value_type(
+ "endloop", type_descriptor(type_endloop, 0)));
+ types.insert(types_map::value_type(
+ "if", type_descriptor(type_if, 1)));
+ types.insert(types_map::value_type(
+ "loop", type_descriptor(type_loop, 2)));
+ return types;
+ }
+
+public:
+ /// The type of the statement.
+ statement_type type;
+
+ /// The arguments to the statement, in textual form.
+ const std::vector< std::string > arguments;
+
+ /// Creates a new statement.
+ ///
+ /// \param type_ The type of the statement.
+ /// \param arguments_ The arguments to the statement.
+ statement_def(const statement_type& type_,
+ const std::vector< std::string >& arguments_) :
+ type(type_), arguments(arguments_)
+ {
+#if !defined(NDEBUG)
+ for (types_map::const_iterator iter = _types.begin();
+ iter != _types.end(); ++iter) {
+ const type_descriptor& descriptor = (*iter).second;
+ if (descriptor.type == type_) {
+ PRE(descriptor.n_arguments == arguments_.size());
+ return;
+ }
+ }
+ UNREACHABLE;
+#endif
+ }
+
+ /// Parses a statement.
+ ///
+ /// \param line The textual representation of the statement without any
+ /// prefix.
+ ///
+ /// \return The parsed statement.
+ ///
+ /// \throw text::syntax_error If the statement is not correctly defined.
+ static statement_def
+ parse(const std::string& line)
+ {
+ if (_types.empty())
+ _types = generate_types_map();
+
+ const std::vector< std::string > words = text::split(line, ' ');
+ if (words.empty())
+ throw text::syntax_error("Empty statement");
+
+ const types_map::const_iterator iter = _types.find(words[0]);
+ if (iter == _types.end())
+ throw text::syntax_error(F("Unknown statement '%s'") % words[0]);
+ const type_descriptor& descriptor = (*iter).second;
+
+ if (words.size() - 1 != descriptor.n_arguments)
+ throw text::syntax_error(F("Invalid number of arguments for "
+ "statement '%s'") % words[0]);
+
+ std::vector< std::string > new_arguments;
+ new_arguments.resize(words.size() - 1);
+ std::copy(words.begin() + 1, words.end(), new_arguments.begin());
+
+ return statement_def(descriptor.type, new_arguments);
+ }
+};
+
+
+statement_def::types_map statement_def::_types;
+
+
+/// Definition of a loop.
+///
+/// This simple structure is used to keep track of the parameters of a loop.
+struct loop_def {
+ /// The name of the vector over which this loop is iterating.
+ std::string vector;
+
+ /// The name of the iterator defined by this loop.
+ std::string iterator;
+
+ /// Position in the input to which to rewind to on looping.
+ ///
+ /// This position points to the line after the loop statement, not the loop
+ /// itself. This is one of the reasons why we have this structure, so that
+ /// we can maintain the data about the loop without having to re-process it.
+ std::istream::pos_type position;
+
+ /// Constructs a new loop definition.
+ ///
+ /// \param vector_ The name of the vector (first argument).
+ /// \param iterator_ The name of the iterator (second argumnet).
+ /// \param position_ Position of the next line after the loop statement.
+ loop_def(const std::string& vector_, const std::string& iterator_,
+ const std::istream::pos_type position_) :
+ vector(vector_), iterator(iterator_), position(position_)
+ {
+ }
+};
+
+
+/// Stateful class to instantiate the templates in an input stream.
+///
+/// The goal of this parser is to scan the input once and not buffer anything in
+/// memory. The only exception are loops: loops are reinterpreted on every
+/// iteration from the same input file by rewidining the stream to the
+/// appropriate position.
+class templates_parser : utils::noncopyable {
+ /// The templates to apply.
+ ///
+ /// Note that this is not const because the parser has to have write access
+ /// to the templates. In particular, it needs to be able to define the
+ /// iterators as regular variables.
+ text::templates_def _templates;
+
+ /// Prefix that marks a line as a statement.
+ const std::string _prefix;
+
+ /// Delimiter to surround an expression instantiation.
+ const std::string _delimiter;
+
+ /// Whether to skip incoming lines or not.
+ ///
+ /// The top of the stack is true whenever we encounter a conditional that
+ /// evaluates to false or a loop that does not have any iterations left.
+ /// Under these circumstances, we need to continue scanning the input stream
+ /// until we find the matching closing endif or endloop construct.
+ ///
+ /// This is a stack rather than a plain boolean to allow us deal with
+ /// if-else clauses.
+ std::stack< bool > _skip;
+
+ /// Current count of nested conditionals.
+ unsigned int _if_level;
+
+ /// Level of the top-most conditional that evaluated to false.
+ unsigned int _exit_if_level;
+
+ /// Current count of nested loops.
+ unsigned int _loop_level;
+
+ /// Level of the top-most loop that does not have any iterations left.
+ unsigned int _exit_loop_level;
+
+ /// Information about all the nested loops up to the current point.
+ std::stack< loop_def > _loops;
+
+ /// Checks if a line is a statement or not.
+ ///
+ /// \param line The line to validate.
+ ///
+ /// \return True if the line looks like a statement, which is determined by
+ /// checking if the line starts by the predefined prefix.
+ bool
+ is_statement(const std::string& line)
+ {
+ return ((line.length() >= _prefix.length() &&
+ line.substr(0, _prefix.length()) == _prefix) &&
+ (line.length() < _delimiter.length() ||
+ line.substr(0, _delimiter.length()) != _delimiter));
+ }
+
+ /// Parses a given statement line into a statement definition.
+ ///
+ /// \param line The line to validate; it must be a valid statement.
+ ///
+ /// \return The parsed statement.
+ ///
+ /// \throw text::syntax_error If the input is not a valid statement.
+ statement_def
+ parse_statement(const std::string& line)
+ {
+ PRE(is_statement(line));
+ return statement_def::parse(line.substr(_prefix.length()));
+ }
+
+ /// Processes a line from the input when not in skip mode.
+ ///
+ /// \param line The line to be processed.
+ /// \param input The input stream from which the line was read. The current
+ /// position in the stream must be after the line being processed.
+ /// \param output The output stream into which to write the results.
+ ///
+ /// \throw text::syntax_error If the input is not valid.
+ void
+ handle_normal(const std::string& line, std::istream& input,
+ std::ostream& output)
+ {
+ if (!is_statement(line)) {
+ // Fast path. Mostly to avoid an indentation level for the big
+ // chunk of code below.
+ output << line << '\n';
+ return;
+ }
+
+ const statement_def statement = parse_statement(line);
+
+ switch (statement.type) {
+ case statement_def::type_else:
+ _skip.top() = !_skip.top();
+ break;
+
+ case statement_def::type_endif:
+ _if_level--;
+ break;
+
+ case statement_def::type_endloop: {
+ PRE(_loops.size() == _loop_level);
+ loop_def& loop = _loops.top();
+
+ const std::size_t next_index = 1 + text::to_type< std::size_t >(
+ _templates.get_variable(loop.iterator));
+
+ if (next_index < _templates.get_vector(loop.vector).size()) {
+ _templates.add_variable(loop.iterator, F("%s") % next_index);
+ input.seekg(loop.position);
+ } else {
+ _loop_level--;
+ _loops.pop();
+ _templates.remove_variable(loop.iterator);
+ }
+ } break;
+
+ case statement_def::type_if: {
+ _if_level++;
+ const std::string value = _templates.evaluate(
+ statement.arguments[0]);
+ if (value.empty() || value == "0" || value == "false") {
+ _exit_if_level = _if_level;
+ _skip.push(true);
+ } else {
+ _skip.push(false);
+ }
+ } break;
+
+ case statement_def::type_loop: {
+ _loop_level++;
+
+ const loop_def loop(statement.arguments[0], statement.arguments[1],
+ input.tellg());
+ if (_templates.get_vector(loop.vector).empty()) {
+ _exit_loop_level = _loop_level;
+ _skip.push(true);
+ } else {
+ _templates.add_variable(loop.iterator, "0");
+ _loops.push(loop);
+ _skip.push(false);
+ }
+ } break;
+ }
+ }
+
+ /// Processes a line from the input when in skip mode.
+ ///
+ /// \param line The line to be processed.
+ ///
+ /// \throw text::syntax_error If the input is not valid.
+ void
+ handle_skip(const std::string& line)
+ {
+ PRE(_skip.top());
+
+ if (!is_statement(line))
+ return;
+
+ const statement_def statement = parse_statement(line);
+ switch (statement.type) {
+ case statement_def::type_else:
+ if (_exit_if_level == _if_level)
+ _skip.top() = !_skip.top();
+ break;
+
+ case statement_def::type_endif:
+ INV(_if_level >= _exit_if_level);
+ if (_if_level == _exit_if_level)
+ _skip.top() = false;
+ _if_level--;
+ _skip.pop();
+ break;
+
+ case statement_def::type_endloop:
+ INV(_loop_level >= _exit_loop_level);
+ if (_loop_level == _exit_loop_level)
+ _skip.top() = false;
+ _loop_level--;
+ _skip.pop();
+ break;
+
+ case statement_def::type_if:
+ _if_level++;
+ _skip.push(true);
+ break;
+
+ case statement_def::type_loop:
+ _loop_level++;
+ _skip.push(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /// Evaluates expressions on a given input line.
+ ///
+ /// An expression is surrounded by _delimiter on both sides. We scan the
+ /// string from left to right finding any expressions that may appear, yank
+ /// them out and call templates_def::evaluate() to get their value.
+ ///
+ /// Lonely or unbalanced appearances of _delimiter on the input line are
+ /// not considered an error, given that the user may actually want to supply
+ /// that character sequence without being interpreted as a template.
+ ///
+ /// \param in_line The input line from which to evaluate expressions.
+ ///
+ /// \return The evaluated line.
+ ///
+ /// \throw text::syntax_error If the expressions in the line are malformed.
+ std::string
+ evaluate(const std::string& in_line)
+ {
+ std::string out_line;
+
+ std::string::size_type last_pos = 0;
+ while (last_pos != std::string::npos) {
+ const std::string::size_type open_pos = in_line.find(
+ _delimiter, last_pos);
+ if (open_pos == std::string::npos) {
+ out_line += in_line.substr(last_pos);
+ last_pos = std::string::npos;
+ } else {
+ const std::string::size_type close_pos = in_line.find(
+ _delimiter, open_pos + _delimiter.length());
+ if (close_pos == std::string::npos) {
+ out_line += in_line.substr(last_pos);
+ last_pos = std::string::npos;
+ } else {
+ out_line += in_line.substr(last_pos, open_pos - last_pos);
+ out_line += _templates.evaluate(in_line.substr(
+ open_pos + _delimiter.length(),
+ close_pos - open_pos - _delimiter.length()));
+ last_pos = close_pos + _delimiter.length();
+ }
+ }
+ }
+
+ return out_line;
+ }
+
+public:
+ /// Constructs a new template parser.
+ ///
+ /// \param templates_ The templates to apply to the processed file.
+ /// \param prefix_ The prefix that identifies lines as statements.
+ /// \param delimiter_ Delimiter to surround a variable instantiation.
+ templates_parser(const text::templates_def& templates_,
+ const std::string& prefix_,
+ const std::string& delimiter_) :
+ _templates(templates_),
+ _prefix(prefix_),
+ _delimiter(delimiter_),
+ _if_level(0),
+ _exit_if_level(0),
+ _loop_level(0),
+ _exit_loop_level(0)
+ {
+ }
+
+ /// Applies the templates to a given input.
+ ///
+ /// \param input The stream to which to apply the templates.
+ /// \param output The stream into which to write the results.
+ ///
+ /// \throw text::syntax_error If the input is not valid. Note that the
+ /// is not guaranteed to be unmodified on exit if an error is
+ /// encountered.
+ void
+ instantiate(std::istream& input, std::ostream& output)
+ {
+ std::string line;
+ while (std::getline(input, line).good()) {
+ if (!_skip.empty() && _skip.top())
+ handle_skip(line);
+ else
+ handle_normal(evaluate(line), input, output);
+ }
+ }
+};
+
+
+} // anonymous namespace
+
+
+/// Constructs an empty templates definition.
+text::templates_def::templates_def(void)
+{
+}
+
+
+/// Sets a string variable in the templates.
+///
+/// If the variable already exists, its value is replaced. This behavior is
+/// required to implement iterators, but client code should really not be
+/// redefining variables.
+///
+/// \pre The variable must not already exist as a vector.
+///
+/// \param name The name of the variable to set.
+/// \param value The value to set the given variable to.
+void
+text::templates_def::add_variable(const std::string& name,
+ const std::string& value)
+{
+ PRE(_vectors.find(name) == _vectors.end());
+ _variables[name] = value;
+}
+
+
+/// Unsets a string variable from the templates.
+///
+/// Client code has no reason to use this. This is only required to implement
+/// proper scoping of loop iterators.
+///
+/// \pre The variable must exist.
+///
+/// \param name The name of the variable to remove from the templates.
+void
+text::templates_def::remove_variable(const std::string& name)
+{
+ PRE(_variables.find(name) != _variables.end());
+ _variables.erase(_variables.find(name));
+}
+
+
+/// Creates a new vector in the templates.
+///
+/// If the vector already exists, it is cleared. Client code should really not
+/// be redefining variables.
+///
+/// \pre The vector must not already exist as a variable.
+///
+/// \param name The name of the vector to set.
+void
+text::templates_def::add_vector(const std::string& name)
+{
+ PRE(_variables.find(name) == _variables.end());
+ _vectors[name] = strings_vector();
+}
+
+
+/// Adds a value to an existing vector in the templates.
+///
+/// \pre name The vector must exist.
+///
+/// \param name The name of the vector to append the value to.
+/// \param value The textual value to append to the vector.
+void
+text::templates_def::add_to_vector(const std::string& name,
+ const std::string& value)
+{
+ PRE(_variables.find(name) == _variables.end());
+ PRE(_vectors.find(name) != _vectors.end());
+ _vectors[name].push_back(value);
+}
+
+
+/// Checks whether a given identifier exists as a variable or a vector.
+///
+/// This is used to implement the evaluation of conditions in if clauses.
+///
+/// \param name The name of the variable or vector.
+///
+/// \return True if the given name exists as a variable or a vector; false
+/// otherwise.
+bool
+text::templates_def::exists(const std::string& name) const
+{
+ return (_variables.find(name) != _variables.end() ||
+ _vectors.find(name) != _vectors.end());
+}
+
+
+/// Gets the value of a variable.
+///
+/// \param name The name of the variable.
+///
+/// \return The value of the requested variable.
+///
+/// \throw text::syntax_error If the variable does not exist.
+const std::string&
+text::templates_def::get_variable(const std::string& name) const
+{
+ const variables_map::const_iterator iter = _variables.find(name);
+ if (iter == _variables.end())
+ throw text::syntax_error(F("Unknown variable '%s'") % name);
+ return (*iter).second;
+}
+
+
+/// Gets a vector.
+///
+/// \param name The name of the vector.
+///
+/// \return A reference to the requested vector.
+///
+/// \throw text::syntax_error If the vector does not exist.
+const text::templates_def::strings_vector&
+text::templates_def::get_vector(const std::string& name) const
+{
+ const vectors_map::const_iterator iter = _vectors.find(name);
+ if (iter == _vectors.end())
+ throw text::syntax_error(F("Unknown vector '%s'") % name);
+ return (*iter).second;
+}
+
+
+/// Indexes a vector and gets the value.
+///
+/// \param name The name of the vector to index.
+/// \param index_name The name of a variable representing the index to use.
+/// This must be convertible to a natural.
+///
+/// \return The value of the vector at the given index.
+///
+/// \throw text::syntax_error If the vector does not existor if the index is out
+/// of range.
+const std::string&
+text::templates_def::get_vector(const std::string& name,
+ const std::string& index_name) const
+{
+ const strings_vector& vector = get_vector(name);
+ const std::string& index_str = get_variable(index_name);
+
+ std::size_t index;
+ try {
+ index = text::to_type< std::size_t >(index_str);
+ } catch (const text::syntax_error& e) {
+ throw text::syntax_error(F("Index '%s' not an integer, value '%s'") %
+ index_name % index_str);
+ }
+ if (index >= vector.size())
+ throw text::syntax_error(F("Index '%s' out of range at position '%s'") %
+ index_name % index);
+
+ return vector[index];
+}
+
+
+/// Evaluates a expression using these templates.
+///
+/// An expression is a query on the current templates to fetch a particular
+/// value. The value is always returned as a string, as this is how templates
+/// are internally stored.
+///
+/// \param expression The expression to evaluate. This should not include any
+/// of the delimiters used in the user input, as otherwise the expression
+/// will not be evaluated properly.
+///
+/// \return The result of the expression evaluation as a string.
+///
+/// \throw text::syntax_error If there is any problem while evaluating the
+/// expression.
+std::string
+text::templates_def::evaluate(const std::string& expression) const
+{
+ const std::string::size_type paren_open = expression.find('(');
+ if (paren_open == std::string::npos) {
+ return get_variable(expression);
+ } else {
+ const std::string::size_type paren_close = expression.find(
+ ')', paren_open);
+ if (paren_close == std::string::npos)
+ throw text::syntax_error(F("Expected ')' in expression '%s')") %
+ expression);
+ if (paren_close != expression.length() - 1)
+ throw text::syntax_error(F("Unexpected text found after ')' in "
+ "expression '%s'") % expression);
+
+ const std::string arg0 = expression.substr(0, paren_open);
+ const std::string arg1 = expression.substr(
+ paren_open + 1, paren_close - paren_open - 1);
+ if (arg0 == "defined") {
+ return exists(arg1) ? "true" : "false";
+ } else if (arg0 == "length") {
+ return F("%s") % get_vector(arg1).size();
+ } else {
+ return get_vector(arg0, arg1);
+ }
+ }
+}
+
+
+/// Applies a set of templates to an input stream.
+///
+/// \param templates The templates to use.
+/// \param input The input to process.
+/// \param output The stream to which to write the processed text.
+///
+/// \throw text::syntax_error If there is any problem processing the input.
+void
+text::instantiate(const templates_def& templates,
+ std::istream& input, std::ostream& output)
+{
+ templates_parser parser(templates, "%", "%%");
+ parser.instantiate(input, output);
+}
+
+
+/// Applies a set of templates to an input file and writes an output file.
+///
+/// \param templates The templates to use.
+/// \param input_file The path to the input to process.
+/// \param output_file The path to the file into which to write the output.
+///
+/// \throw text::error If the input or output files cannot be opened.
+/// \throw text::syntax_error If there is any problem processing the input.
+void
+text::instantiate(const templates_def& templates,
+ const fs::path& input_file, const fs::path& output_file)
+{
+ std::ifstream input(input_file.c_str());
+ if (!input)
+ throw text::error(F("Failed to open %s for read") % input_file);
+
+ std::ofstream output(output_file.c_str());
+ if (!output)
+ throw text::error(F("Failed to open %s for write") % output_file);
+
+ instantiate(templates, input, output);
+}
diff --git a/utils/text/templates.hpp b/utils/text/templates.hpp
new file mode 100644
index 000000000000..ffbf28512d0d
--- /dev/null
+++ b/utils/text/templates.hpp
@@ -0,0 +1,122 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/templates.hpp
+/// Custom templating engine for text documents.
+///
+/// This module provides a simple mechanism to generate text documents based on
+/// templates. The templates are just text files that contain template
+/// statements that instruct this processor to perform transformations on the
+/// input.
+///
+/// While this was originally written to handle HTML templates, it is actually
+/// generic enough to handle any kind of text document, hence why it lives
+/// within the utils::text library.
+///
+/// An example of how the templates look like:
+///
+/// %if names
+/// List of names
+/// -------------
+/// Amount of names: %%length(names)%%
+/// Most preferred name: %%preferred_name%%
+/// Full list:
+/// %loop names iter
+/// * %%last_names(iter)%%, %%names(iter)%%
+/// %endloop
+/// %endif names
+
+#if !defined(UTILS_TEXT_TEMPLATES_HPP)
+#define UTILS_TEXT_TEMPLATES_HPP
+
+#include "utils/text/templates_fwd.hpp"
+
+#include <istream>
+#include <map>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace text {
+
+
+/// Definitions of the templates to apply to a file.
+///
+/// This class provides the environment (e.g. the list of variables) that the
+/// templating system has to use when generating the output files. This
+/// definition is static in the sense that this is what the caller program
+/// specifies.
+class templates_def {
+ /// Mapping of variable names to their values.
+ typedef std::map< std::string, std::string > variables_map;
+
+ /// Collection of global variables available to the templates.
+ variables_map _variables;
+
+ /// Convenience name for a vector of strings.
+ typedef std::vector< std::string > strings_vector;
+
+ /// Mapping of vector names to their contents.
+ ///
+ /// Ideally, these would be represented as part of the _variables, but we
+ /// would need a complex mechanism to identify whether a variable is a
+ /// string or a vector.
+ typedef std::map< std::string, strings_vector > vectors_map;
+
+ /// Collection of vectors available to the templates.
+ vectors_map _vectors;
+
+ const std::string& get_vector(const std::string&, const std::string&) const;
+
+public:
+ templates_def(void);
+
+ void add_variable(const std::string&, const std::string&);
+ void remove_variable(const std::string&);
+ void add_vector(const std::string&);
+ void add_to_vector(const std::string&, const std::string&);
+
+ bool exists(const std::string&) const;
+ const std::string& get_variable(const std::string&) const;
+ const strings_vector& get_vector(const std::string&) const;
+
+ std::string evaluate(const std::string&) const;
+};
+
+
+void instantiate(const templates_def&, std::istream&, std::ostream&);
+void instantiate(const templates_def&, const fs::path&, const fs::path&);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TEMPLATES_HPP)
diff --git a/utils/text/templates_fwd.hpp b/utils/text/templates_fwd.hpp
new file mode 100644
index 000000000000..c806be0cf497
--- /dev/null
+++ b/utils/text/templates_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/templates_fwd.hpp
+/// Forward declarations for utils/text/templates.hpp
+
+#if !defined(UTILS_TEXT_TEMPLATES_FWD_HPP)
+#define UTILS_TEXT_TEMPLATES_FWD_HPP
+
+namespace utils {
+namespace text {
+
+
+class templates_def;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TEMPLATES_FWD_HPP)
diff --git a/utils/text/templates_test.cpp b/utils/text/templates_test.cpp
new file mode 100644
index 000000000000..4524dc61a416
--- /dev/null
+++ b/utils/text/templates_test.cpp
@@ -0,0 +1,1001 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/templates.hpp"
+
+#include <fstream>
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/text/exceptions.hpp"
+
+namespace fs = utils::fs;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Applies a set of templates to an input string and validates the output.
+///
+/// This fails the test case if exp_output does not match the document generated
+/// by the application of the templates.
+///
+/// \param templates The templates to apply.
+/// \param input_str The input document to which to apply the templates.
+/// \param exp_output The expected output document.
+static void
+do_test_ok(const text::templates_def& templates, const std::string& input_str,
+ const std::string& exp_output)
+{
+ std::istringstream input(input_str);
+ std::ostringstream output;
+
+ text::instantiate(templates, input, output);
+ ATF_REQUIRE_EQ(exp_output, output.str());
+}
+
+
+/// Applies a set of templates to an input string and checks for an error.
+///
+/// This fails the test case if the exception raised by the template processing
+/// does not match the expected message.
+///
+/// \param templates The templates to apply.
+/// \param input_str The input document to which to apply the templates.
+/// \param exp_message The expected error message in the raised exception.
+static void
+do_test_fail(const text::templates_def& templates, const std::string& input_str,
+ const std::string& exp_message)
+{
+ std::istringstream input(input_str);
+ std::ostringstream output;
+
+ ATF_REQUIRE_THROW_RE(text::syntax_error, exp_message,
+ text::instantiate(templates, input, output));
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__first);
+ATF_TEST_CASE_BODY(templates_def__add_variable__first)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "first-value");
+ ATF_REQUIRE_EQ("first-value", templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__replace);
+ATF_TEST_CASE_BODY(templates_def__add_variable__replace)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "first-value");
+ templates.add_variable("the-name", "second-value");
+ ATF_REQUIRE_EQ("second-value", templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__remove_variable);
+ATF_TEST_CASE_BODY(templates_def__remove_variable)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "the-value");
+ templates.get_variable("the-name"); // Should not throw.
+ templates.remove_variable("the-name");
+ ATF_REQUIRE_THROW(text::syntax_error, templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__first);
+ATF_TEST_CASE_BODY(templates_def__add_vector__first)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ ATF_REQUIRE(templates.get_vector("the-name").empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__replace);
+ATF_TEST_CASE_BODY(templates_def__add_vector__replace)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ templates.add_to_vector("the-name", "foo");
+ ATF_REQUIRE(!templates.get_vector("the-name").empty());
+ templates.add_vector("the-name");
+ ATF_REQUIRE(templates.get_vector("the-name").empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_to_vector);
+ATF_TEST_CASE_BODY(templates_def__add_to_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ ATF_REQUIRE_EQ(0, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "first");
+ ATF_REQUIRE_EQ(1, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "second");
+ ATF_REQUIRE_EQ(2, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "third");
+ ATF_REQUIRE_EQ(3, templates.get_vector("the-name").size());
+
+ std::vector< std::string > expected;
+ expected.push_back("first");
+ expected.push_back("second");
+ expected.push_back("third");
+ ATF_REQUIRE(expected == templates.get_vector("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__variable);
+ATF_TEST_CASE_BODY(templates_def__exists__variable)
+{
+ text::templates_def templates;
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_variable("some-name ", "foo");
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_variable("some-name", "foo");
+ ATF_REQUIRE(templates.exists("some-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__vector);
+ATF_TEST_CASE_BODY(templates_def__exists__vector)
+{
+ text::templates_def templates;
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_vector("some-name ");
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_vector("some-name");
+ ATF_REQUIRE(templates.exists("some-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__ok);
+ATF_TEST_CASE_BODY(templates_def__get_variable__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ templates.add_variable("bar", " baz ");
+ ATF_REQUIRE_EQ("", templates.get_variable("foo"));
+ ATF_REQUIRE_EQ(" baz ", templates.get_variable("bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__unknown);
+ATF_TEST_CASE_BODY(templates_def__get_variable__unknown)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo '",
+ templates.get_variable("foo "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__ok);
+ATF_TEST_CASE_BODY(templates_def__get_vector__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("foo");
+ templates.add_vector("bar");
+ templates.add_to_vector("bar", "baz");
+ ATF_REQUIRE_EQ(0, templates.get_vector("foo").size());
+ ATF_REQUIRE_EQ(1, templates.get_vector("bar").size());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__unknown);
+ATF_TEST_CASE_BODY(templates_def__get_vector__unknown)
+{
+ text::templates_def templates;
+ templates.add_vector("foo");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo '",
+ templates.get_vector("foo "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__variable__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ templates.add_variable("bar", " baz ");
+ ATF_REQUIRE_EQ("", templates.evaluate("foo"));
+ ATF_REQUIRE_EQ(" baz ", templates.evaluate("bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__unknown);
+ATF_TEST_CASE_BODY(templates_def__evaluate__variable__unknown)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo1'",
+ templates.evaluate("foo1"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_to_vector("v", "bar");
+ templates.add_to_vector("v", "baz");
+
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_EQ("foo", templates.evaluate("v(index)"));
+ templates.add_variable("index", "1");
+ ATF_REQUIRE_EQ("bar", templates.evaluate("v(index)"));
+ templates.add_variable("index", "2");
+ ATF_REQUIRE_EQ("baz", templates.evaluate("v(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_vector);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'fooz'",
+ templates.evaluate("fooz(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_index);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_index)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'indexz'",
+ templates.evaluate("v(indexz)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__out_of_range);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__out_of_range)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "1");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Index 'index' out of range "
+ "at position '1'", templates.evaluate("v(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__defined);
+ATF_TEST_CASE_BODY(templates_def__evaluate__defined)
+{
+ text::templates_def templates;
+ templates.add_vector("the-variable");
+ templates.add_vector("the-vector");
+ ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-variabl)"));
+ ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-vecto)"));
+ ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-variable)"));
+ ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-vector)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__length__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ ATF_REQUIRE_EQ("0", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "foo");
+ ATF_REQUIRE_EQ("1", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "bar");
+ ATF_REQUIRE_EQ("2", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "baz");
+ ATF_REQUIRE_EQ("3", templates.evaluate("length(v)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__unknown_vector);
+ATF_TEST_CASE_BODY(templates_def__evaluate__length__unknown_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("foo1");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo'",
+ templates.evaluate("length(foo)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__parenthesis_error);
+ATF_TEST_CASE_BODY(templates_def__evaluate__parenthesis_error)
+{
+ text::templates_def templates;
+ ATF_REQUIRE_THROW_RE(text::syntax_error,
+ "Expected '\\)' in.*'foo\\(abc'",
+ templates.evaluate("foo(abc"));
+ ATF_REQUIRE_THROW_RE(text::syntax_error,
+ "Unexpected text.*'\\)' in.*'a\\(b\\)c'",
+ templates.evaluate("a(b)c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_input);
+ATF_TEST_CASE_BODY(instantiate__empty_input)
+{
+ const text::templates_def templates;
+ do_test_ok(templates, "", "");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__ok);
+ATF_TEST_CASE_BODY(instantiate__value__ok)
+{
+ const std::string input =
+ "first line\n"
+ "%%testvar1%%\n"
+ "third line\n"
+ "%%testvar2%% %%testvar3%%%%testvar4%%\n"
+ "fifth line\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "second line\n"
+ "third line\n"
+ "fourth line.\n"
+ "fifth line\n";
+
+ text::templates_def templates;
+ templates.add_variable("testvar1", "second line");
+ templates.add_variable("testvar2", "fourth");
+ templates.add_variable("testvar3", "line");
+ templates.add_variable("testvar4", ".");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__unknown_variable);
+ATF_TEST_CASE_BODY(instantiate__value__unknown_variable)
+{
+ const std::string input =
+ "%%testvar1%%\n";
+
+ text::templates_def templates;
+ templates.add_variable("testvar2", "fourth line");
+
+ do_test_fail(templates, input, "Unknown variable 'testvar1'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__ok);
+ATF_TEST_CASE_BODY(instantiate__vector_length__ok)
+{
+ const std::string input =
+ "%%length(testvector1)%%\n"
+ "%%length(testvector2)%% - %%length(testvector3)%%\n";
+
+ const std::string exp_output =
+ "4\n"
+ "0 - 1\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector1");
+ templates.add_to_vector("testvector1", "000");
+ templates.add_to_vector("testvector1", "111");
+ templates.add_to_vector("testvector1", "543");
+ templates.add_to_vector("testvector1", "999");
+ templates.add_vector("testvector2");
+ templates.add_vector("testvector3");
+ templates.add_to_vector("testvector3", "123");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__unknown_vector);
+ATF_TEST_CASE_BODY(instantiate__vector_length__unknown_vector)
+{
+ const std::string input =
+ "%%length(testvector)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector2");
+
+ do_test_fail(templates, input, "Unknown vector 'testvector'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__ok);
+ATF_TEST_CASE_BODY(instantiate__vector_value__ok)
+{
+ const std::string input =
+ "first line\n"
+ "%%testvector1(i)%%\n"
+ "third line\n"
+ "%%testvector2(j)%%\n"
+ "fifth line\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "543\n"
+ "third line\n"
+ "123\n"
+ "fifth line\n";
+
+ text::templates_def templates;
+ templates.add_variable("i", "2");
+ templates.add_variable("j", "0");
+ templates.add_vector("testvector1");
+ templates.add_to_vector("testvector1", "000");
+ templates.add_to_vector("testvector1", "111");
+ templates.add_to_vector("testvector1", "543");
+ templates.add_to_vector("testvector1", "999");
+ templates.add_vector("testvector2");
+ templates.add_to_vector("testvector2", "123");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__unknown_vector);
+ATF_TEST_CASE_BODY(instantiate__vector_value__unknown_vector)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector2");
+
+ do_test_fail(templates, input, "Unknown vector 'testvector'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__empty);
+ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__empty)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector");
+ templates.add_variable("j", "0");
+
+ do_test_fail(templates, input, "Index 'j' out of range at position '0'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__not_empty);
+ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__not_empty)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector");
+ templates.add_to_vector("testvector", "a");
+ templates.add_to_vector("testvector", "b");
+ templates.add_variable("j", "2");
+
+ do_test_fail(templates, input, "Index 'j' out of range at position '2'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__taken);
+ATF_TEST_CASE_BODY(instantiate__if__one_level__taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(some_var)\n"
+ "hello from within the variable conditional\n"
+ "%endif\n"
+ "%if defined(some_vector)\n"
+ "hello from within the vector conditional\n"
+ "%else\n"
+ "bye from within the vector conditional\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "hello from within the variable conditional\n"
+ "hello from within the vector conditional\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("some_var", "zzz");
+ templates.add_vector("some_vector");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__not_taken);
+ATF_TEST_CASE_BODY(instantiate__if__one_level__not_taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(some_var)\n"
+ "hello from within the variable conditional\n"
+ "%endif\n"
+ "%if defined(some_vector)\n"
+ "hello from within the vector conditional\n"
+ "%else\n"
+ "bye from within the vector conditional\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "bye from within the vector conditional\n"
+ "some more\n";
+
+ text::templates_def templates;
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__taken);
+ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(var1)\n"
+ "first before\n"
+ "%if length(var2)\n"
+ "second before\n"
+ "%if defined(var3)\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "%endif\n"
+ "second after\n"
+ "%else\n"
+ "second after not shown\n"
+ "%endif\n"
+ "first after\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "first before\n"
+ "second before\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "second after\n"
+ "first after\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "false");
+ templates.add_vector("var2");
+ templates.add_to_vector("var2", "not-empty");
+ templates.add_variable("var3", "foobar");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__not_taken);
+ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__not_taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(var1)\n"
+ "first before\n"
+ "%if length(var2)\n"
+ "second before\n"
+ "%if defined(var3)\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "%else\n"
+ "will not be shown either\n"
+ "%endif\n"
+ "second after\n"
+ "%else\n"
+ "second after shown\n"
+ "%endif\n"
+ "first after\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "first before\n"
+ "second after shown\n"
+ "first after\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "false");
+ templates.add_vector("var2");
+ templates.add_vector("var3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__no_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__no_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "hello\n"
+ "value in vector: %%table1(i)%%\n"
+ "%if defined(var1)\n" "some other text\n" "%endif\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "defined");
+ templates.add_vector("table1");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__multiple_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__multiple_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "hello %%table1(i)%% %%table2(i)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "hello foo1 foo2\n"
+ "hello bar1 bar2\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "foo1");
+ templates.add_to_vector("table1", "bar1");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "foo2");
+ templates.add_to_vector("table2", "bar2");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__no_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__nested__no_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "before: %%table1(i)%%\n"
+ "%loop table2 j\n"
+ "before: %%table2(j)%%\n"
+ "%loop table3 k\n"
+ "%%table3(k)%%\n"
+ "%endloop\n"
+ "after: %%table2(i)%%\n"
+ "%endloop\n"
+ "after: %%table1(i)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "before: a\n"
+ "after: a\n"
+ "before: b\n"
+ "after: b\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_vector("table3");
+ templates.add_to_vector("table3", "1");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__multiple_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__nested__multiple_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "%loop table2 j\n"
+ "%%table1(i)%% %%table2(j)%%\n"
+ "%endloop\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "a 1\n"
+ "a 2\n"
+ "a 3\n"
+ "b 1\n"
+ "b 2\n"
+ "b 3\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "1");
+ templates.add_to_vector("table2", "2");
+ templates.add_to_vector("table2", "3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__sequential);
+ATF_TEST_CASE_BODY(instantiate__loop__sequential)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 iter\n"
+ "1: %%table1(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table2 iter\n"
+ "2: %%table2(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table3 iter\n"
+ "3: %%table3(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table4 iter\n"
+ "4: %%table4(iter)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "1: a\n"
+ "1: b\n"
+ "divider\n"
+ "divider\n"
+ "divider\n"
+ "4: 1\n"
+ "4: 2\n"
+ "4: 3\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_vector("table3");
+ templates.add_vector("table4");
+ templates.add_to_vector("table4", "1");
+ templates.add_to_vector("table4", "2");
+ templates.add_to_vector("table4", "3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__scoping);
+ATF_TEST_CASE_BODY(instantiate__loop__scoping)
+{
+ const std::string input =
+ "%loop table1 i\n"
+ "%if defined(i)\n" "i defined inside scope 1\n" "%endif\n"
+ "%loop table2 j\n"
+ "%if defined(i)\n" "i defined inside scope 2\n" "%endif\n"
+ "%if defined(j)\n" "j defined inside scope 2\n" "%endif\n"
+ "%endloop\n"
+ "%if defined(j)\n" "j defined inside scope 1\n" "%endif\n"
+ "%endloop\n"
+ "%if defined(i)\n" "i defined outside\n" "%endif\n"
+ "%if defined(j)\n" "j defined outside\n" "%endif\n";
+
+ const std::string exp_output =
+ "i defined inside scope 1\n"
+ "i defined inside scope 2\n"
+ "j defined inside scope 2\n"
+ "i defined inside scope 1\n"
+ "i defined inside scope 2\n"
+ "j defined inside scope 2\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "first");
+ templates.add_to_vector("table1", "second");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "first");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__mismatched_delimiters);
+ATF_TEST_CASE_BODY(instantiate__mismatched_delimiters)
+{
+ const std::string input =
+ "this is some %% text\n"
+ "and this is %%var%% text%%\n";
+
+ const std::string exp_output =
+ "this is some %% text\n"
+ "and this is some more text%%\n";
+
+ text::templates_def templates;
+ templates.add_variable("var", "some more");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_statement);
+ATF_TEST_CASE_BODY(instantiate__empty_statement)
+{
+ do_test_fail(text::templates_def(), "%\n", "Empty statement");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__unknown_statement);
+ATF_TEST_CASE_BODY(instantiate__unknown_statement)
+{
+ do_test_fail(text::templates_def(), "%if2\n", "Unknown statement 'if2'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__invalid_narguments);
+ATF_TEST_CASE_BODY(instantiate__invalid_narguments)
+{
+ do_test_fail(text::templates_def(), "%if a b\n",
+ "Invalid number of arguments for statement 'if'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__ok);
+ATF_TEST_CASE_BODY(instantiate__files__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("string", "Hello, world!");
+
+ atf::utils::create_file("input.txt", "The string is: %%string%%\n");
+
+ text::instantiate(templates, fs::path("input.txt"), fs::path("output.txt"));
+
+ std::ifstream output("output.txt");
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ(line, "The string is: Hello, world!");
+ ATF_REQUIRE(std::getline(output, line).eof());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__input_error);
+ATF_TEST_CASE_BODY(instantiate__files__input_error)
+{
+ text::templates_def templates;
+ ATF_REQUIRE_THROW_RE(text::error, "Failed to open input.txt for read",
+ text::instantiate(templates, fs::path("input.txt"),
+ fs::path("output.txt")));
+}
+
+
+ATF_TEST_CASE(instantiate__files__output_error);
+ATF_TEST_CASE_HEAD(instantiate__files__output_error)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(instantiate__files__output_error)
+{
+ text::templates_def templates;
+
+ atf::utils::create_file("input.txt", "");
+
+ fs::mkdir(fs::path("dir"), 0444);
+
+ ATF_REQUIRE_THROW_RE(text::error, "Failed to open dir/output.txt for write",
+ text::instantiate(templates, fs::path("input.txt"),
+ fs::path("dir/output.txt")));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__first);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__replace);
+ ATF_ADD_TEST_CASE(tcs, templates_def__remove_variable);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__first);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__replace);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_to_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__exists__variable);
+ ATF_ADD_TEST_CASE(tcs, templates_def__exists__vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_index);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__out_of_range);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__defined);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__parenthesis_error);
+
+ ATF_ADD_TEST_CASE(tcs, instantiate__empty_input);
+ ATF_ADD_TEST_CASE(tcs, instantiate__value__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__value__unknown_variable);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__empty);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__not_empty);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__not_taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__not_taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__no_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__multiple_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__no_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__multiple_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__sequential);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__scoping);
+ ATF_ADD_TEST_CASE(tcs, instantiate__mismatched_delimiters);
+ ATF_ADD_TEST_CASE(tcs, instantiate__empty_statement);
+ ATF_ADD_TEST_CASE(tcs, instantiate__unknown_statement);
+ ATF_ADD_TEST_CASE(tcs, instantiate__invalid_narguments);
+
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__input_error);
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__output_error);
+}
diff --git a/utils/units.cpp b/utils/units.cpp
new file mode 100644
index 000000000000..bfb488fa2ed6
--- /dev/null
+++ b/utils/units.cpp
@@ -0,0 +1,172 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/units.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace units = utils::units;
+
+
+/// Constructs a zero bytes quantity.
+units::bytes::bytes(void) :
+ _count(0)
+{
+}
+
+
+/// Constructs an arbitrary bytes quantity.
+///
+/// \param count_ The amount of bytes in the quantity.
+units::bytes::bytes(const uint64_t count_) :
+ _count(count_)
+{
+}
+
+
+/// Parses a string into a bytes quantity.
+///
+/// \param in_str The user-provided string to be converted.
+///
+/// \return The converted bytes quantity.
+///
+/// \throw std::runtime_error If the input string is empty or invalid.
+units::bytes
+units::bytes::parse(const std::string& in_str)
+{
+ if (in_str.empty())
+ throw std::runtime_error("Bytes quantity cannot be empty");
+
+ uint64_t multiplier;
+ std::string str = in_str;
+ {
+ const char unit = str[str.length() - 1];
+ switch (unit) {
+ case 'T': case 't': multiplier = TB; break;
+ case 'G': case 'g': multiplier = GB; break;
+ case 'M': case 'm': multiplier = MB; break;
+ case 'K': case 'k': multiplier = KB; break;
+ default: multiplier = 1;
+ }
+ if (multiplier != 1)
+ str.erase(str.length() - 1);
+ }
+
+ if (str.empty())
+ throw std::runtime_error("Bytes quantity cannot be empty");
+ if (str[0] == '.' || str[str.length() - 1] == '.') {
+ // The standard parser for float values accepts things like ".3" and
+ // "3.", which means that we would interpret ".3K" and "3.K" as valid
+ // quantities. I think this is ugly and should not be allowed, so
+ // special-case this condition and just error out.
+ throw std::runtime_error(F("Invalid bytes quantity '%s'") % in_str);
+ }
+
+ double count;
+ try {
+ count = text::to_type< double >(str);
+ } catch (const text::value_error& e) {
+ throw std::runtime_error(F("Invalid bytes quantity '%s'") % in_str);
+ }
+
+ return bytes(uint64_t(count * multiplier));
+}
+
+
+/// Formats a bytes quantity for user consumption.
+///
+/// \return A textual representation of the bytes quantiy.
+std::string
+units::bytes::format(void) const
+{
+ if (_count >= TB) {
+ return F("%.2sT") % (static_cast< float >(_count) / TB);
+ } else if (_count >= GB) {
+ return F("%.2sG") % (static_cast< float >(_count) / GB);
+ } else if (_count >= MB) {
+ return F("%.2sM") % (static_cast< float >(_count) / MB);
+ } else if (_count >= KB) {
+ return F("%.2sK") % (static_cast< float >(_count) / KB);
+ } else {
+ return F("%s") % _count;
+ }
+}
+
+
+/// Implicit conversion to an integral representation.
+units::bytes::operator uint64_t(void) const
+{
+ return _count;
+}
+
+
+/// Extracts a bytes quantity from a stream.
+///
+/// \param input The stream from which to read a single word representing the
+/// bytes quantity.
+/// \param rhs The variable into which to store the parsed value.
+///
+/// \return The input stream.
+///
+/// \post The bad bit of input is set to 1 if the parsing failed.
+std::istream&
+units::operator>>(std::istream& input, bytes& rhs)
+{
+ std::string word;
+ input >> word;
+ if (input.good() || input.eof()) {
+ try {
+ rhs = bytes::parse(word);
+ } catch (const std::runtime_error& e) {
+ input.setstate(std::ios::badbit);
+ }
+ }
+ return input;
+}
+
+
+/// Injects a bytes quantity into a stream.
+///
+/// \param output The stream into which to inject the bytes quantity as a
+/// user-readable string.
+/// \param rhs The bytes quantity to format.
+///
+/// \return The output stream.
+std::ostream&
+units::operator<<(std::ostream& output, const bytes& rhs)
+{
+ return (output << rhs.format());
+}
diff --git a/utils/units.hpp b/utils/units.hpp
new file mode 100644
index 000000000000..281788c3199f
--- /dev/null
+++ b/utils/units.hpp
@@ -0,0 +1,96 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/units.hpp
+/// Formatters and parsers of user-friendly units.
+
+#if !defined(UTILS_UNITS_HPP)
+#define UTILS_UNITS_HPP
+
+#include "utils/units_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <istream>
+#include <ostream>
+#include <string>
+
+namespace utils {
+namespace units {
+
+
+namespace {
+
+/// Constant representing 1 kilobyte.
+const uint64_t KB = int64_t(1) << 10;
+
+/// Constant representing 1 megabyte.
+const uint64_t MB = int64_t(1) << 20;
+
+/// Constant representing 1 gigabyte.
+const uint64_t GB = int64_t(1) << 30;
+
+/// Constant representing 1 terabyte.
+const uint64_t TB = int64_t(1) << 40;
+
+} // anonymous namespace
+
+
+/// Representation of a bytes quantity.
+///
+/// The purpose of this class is to represent an amount of bytes in a semantic
+/// manner, and to provide functions to format such numbers for nice user
+/// presentation and to parse back such numbers.
+///
+/// The input follows this regular expression: [0-9]+(|\.[0-9]+)[GgKkMmTt]?
+/// The output follows this regular expression: [0-9]+\.[0-9]{3}[GKMT]?
+class bytes {
+ /// Raw representation, in bytes, of the quantity.
+ uint64_t _count;
+
+public:
+ bytes(void);
+ explicit bytes(const uint64_t);
+
+ static bytes parse(const std::string&);
+ std::string format(void) const;
+
+ operator uint64_t(void) const;
+};
+
+
+std::istream& operator>>(std::istream&, bytes&);
+std::ostream& operator<<(std::ostream&, const bytes&);
+
+
+} // namespace units
+} // namespace utils
+
+#endif // !defined(UTILS_UNITS_HPP)
diff --git a/utils/units_fwd.hpp b/utils/units_fwd.hpp
new file mode 100644
index 000000000000..3653d9727a2d
--- /dev/null
+++ b/utils/units_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/units_fwd.hpp
+/// Forward declarations for utils/units.hpp
+
+#if !defined(UTILS_UNITS_FWD_HPP)
+#define UTILS_UNITS_FWD_HPP
+
+namespace utils {
+namespace units {
+
+
+class bytes;
+
+
+} // namespace units
+} // namespace utils
+
+#endif // !defined(UTILS_UNITS_FWD_HPP)
diff --git a/utils/units_test.cpp b/utils/units_test.cpp
new file mode 100644
index 000000000000..601265c95b49
--- /dev/null
+++ b/utils/units_test.cpp
@@ -0,0 +1,248 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/units.hpp"
+
+#include <sstream>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+namespace units = utils::units;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__format__tb);
+ATF_TEST_CASE_BODY(bytes__format__tb)
+{
+ using units::TB;
+ using units::GB;
+
+ ATF_REQUIRE_EQ("2.00T", units::bytes(2 * TB).format());
+ ATF_REQUIRE_EQ("45.12T", units::bytes(45 * TB + 120 * GB).format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__format__gb);
+ATF_TEST_CASE_BODY(bytes__format__gb)
+{
+ using units::GB;
+ using units::MB;
+
+ ATF_REQUIRE_EQ("5.00G", units::bytes(5 * GB).format());
+ ATF_REQUIRE_EQ("745.96G", units::bytes(745 * GB + 980 * MB).format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__format__mb);
+ATF_TEST_CASE_BODY(bytes__format__mb)
+{
+ using units::MB;
+ using units::KB;
+
+ ATF_REQUIRE_EQ("1.00M", units::bytes(1 * MB).format());
+ ATF_REQUIRE_EQ("1023.50M", units::bytes(1023 * MB + 512 * KB).format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__format__kb);
+ATF_TEST_CASE_BODY(bytes__format__kb)
+{
+ using units::KB;
+
+ ATF_REQUIRE_EQ("3.00K", units::bytes(3 * KB).format());
+ ATF_REQUIRE_EQ("1.33K", units::bytes(1 * KB + 340).format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__format__b);
+ATF_TEST_CASE_BODY(bytes__format__b)
+{
+ ATF_REQUIRE_EQ("0", units::bytes().format());
+ ATF_REQUIRE_EQ("0", units::bytes(0).format());
+ ATF_REQUIRE_EQ("1023", units::bytes(1023).format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__tb);
+ATF_TEST_CASE_BODY(bytes__parse__tb)
+{
+ using units::TB;
+ using units::GB;
+
+ ATF_REQUIRE_EQ(0, units::bytes::parse("0T"));
+ ATF_REQUIRE_EQ(units::bytes(TB), units::bytes::parse("1T"));
+ ATF_REQUIRE_EQ(units::bytes(TB), units::bytes::parse("1t"));
+ ATF_REQUIRE_EQ(13567973486755LL, units::bytes::parse("12.340000T"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__gb);
+ATF_TEST_CASE_BODY(bytes__parse__gb)
+{
+ using units::GB;
+ using units::MB;
+
+ ATF_REQUIRE_EQ(0, units::bytes::parse("0G"));
+ ATF_REQUIRE_EQ(units::bytes(GB), units::bytes::parse("1G"));
+ ATF_REQUIRE_EQ(units::bytes(GB), units::bytes::parse("1g"));
+ ATF_REQUIRE_EQ(13249974108LL, units::bytes::parse("12.340G"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__mb);
+ATF_TEST_CASE_BODY(bytes__parse__mb)
+{
+ using units::MB;
+ using units::KB;
+
+ ATF_REQUIRE_EQ(0, units::bytes::parse("0M"));
+ ATF_REQUIRE_EQ(units::bytes(MB), units::bytes::parse("1M"));
+ ATF_REQUIRE_EQ(units::bytes(MB), units::bytes::parse("1m"));
+ ATF_REQUIRE_EQ(12939427, units::bytes::parse("12.34000M"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__kb);
+ATF_TEST_CASE_BODY(bytes__parse__kb)
+{
+ using units::KB;
+
+ ATF_REQUIRE_EQ(0, units::bytes::parse("0K"));
+ ATF_REQUIRE_EQ(units::bytes(KB), units::bytes::parse("1K"));
+ ATF_REQUIRE_EQ(units::bytes(KB), units::bytes::parse("1k"));
+ ATF_REQUIRE_EQ(12636, units::bytes::parse("12.34K"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__b);
+ATF_TEST_CASE_BODY(bytes__parse__b)
+{
+ ATF_REQUIRE_EQ(0, units::bytes::parse("0"));
+ ATF_REQUIRE_EQ(89, units::bytes::parse("89"));
+ ATF_REQUIRE_EQ(1234, units::bytes::parse("1234"));
+ ATF_REQUIRE_EQ(1234567890, units::bytes::parse("1234567890"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__parse__error);
+ATF_TEST_CASE_BODY(bytes__parse__error)
+{
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "empty", units::bytes::parse(""));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "empty", units::bytes::parse("k"));
+
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'.'",
+ units::bytes::parse("."));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'3.'",
+ units::bytes::parse("3."));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'.3'",
+ units::bytes::parse(".3"));
+
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*' t'",
+ units::bytes::parse(" t"));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'.t'",
+ units::bytes::parse(".t"));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'12 t'",
+ units::bytes::parse("12 t"));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'12.t'",
+ units::bytes::parse("12.t"));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'.12t'",
+ units::bytes::parse(".12t"));
+ ATF_REQUIRE_THROW_RE(std::runtime_error, "Invalid.*'abt'",
+ units::bytes::parse("abt"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__istream__one_word);
+ATF_TEST_CASE_BODY(bytes__istream__one_word)
+{
+ std::istringstream input("12M");
+
+ units::bytes bytes;
+ input >> bytes;
+ ATF_REQUIRE(input.eof());
+ ATF_REQUIRE_EQ(units::bytes(12 * units::MB), bytes);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__istream__many_words);
+ATF_TEST_CASE_BODY(bytes__istream__many_words)
+{
+ std::istringstream input("12M more");
+
+ units::bytes bytes;
+ input >> bytes;
+ ATF_REQUIRE(input.good());
+ ATF_REQUIRE_EQ(units::bytes(12 * units::MB), bytes);
+
+ std::string word;
+ input >> word;
+ ATF_REQUIRE_EQ("more", word);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__istream__error);
+ATF_TEST_CASE_BODY(bytes__istream__error)
+{
+ std::istringstream input("12.M more");
+
+ units::bytes bytes(123456789);
+ input >> bytes;
+ ATF_REQUIRE(input.bad());
+ ATF_REQUIRE_EQ(units::bytes(123456789), bytes);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bytes__ostream);
+ATF_TEST_CASE_BODY(bytes__ostream)
+{
+ std::ostringstream output;
+ output << "foo " << units::bytes(5 * units::KB) << " bar";
+ ATF_REQUIRE_EQ("foo 5.00K bar", output.str());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, bytes__format__tb);
+ ATF_ADD_TEST_CASE(tcs, bytes__format__gb);
+ ATF_ADD_TEST_CASE(tcs, bytes__format__mb);
+ ATF_ADD_TEST_CASE(tcs, bytes__format__kb);
+ ATF_ADD_TEST_CASE(tcs, bytes__format__b);
+
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__tb);
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__gb);
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__mb);
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__kb);
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__b);
+ ATF_ADD_TEST_CASE(tcs, bytes__parse__error);
+
+ ATF_ADD_TEST_CASE(tcs, bytes__istream__one_word);
+ ATF_ADD_TEST_CASE(tcs, bytes__istream__many_words);
+ ATF_ADD_TEST_CASE(tcs, bytes__istream__error);
+ ATF_ADD_TEST_CASE(tcs, bytes__ostream);
+}