[tpmd] initial commit
Add interface, dbus ACLs, design docs, and skeleton implementation (enough to
pass the autotest, at any rate).
TEST=security_Tpmd
BUG=chromium-os:32448
Signed-off-by: Elly Jones <ellyjones@chromium.org>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bfa3494
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Stub makefile.
+
+# Make sure a passed in OUT= isn't lost.
+OUT ?= $(PWD)/build-opt
+$(filter-out _all,$(MAKECMDGOALS)) _all:
+ $(MAKE) -C src $(MAKECMDGOALS) OUT=$(OUT)
diff --git a/doc/design.md b/doc/design.md
new file mode 100644
index 0000000..7421af7
--- /dev/null
+++ b/doc/design.md
@@ -0,0 +1,35 @@
+# Design doc: tpmd
+
+[iface]: ../share/org.chromium.tpmd.xml
+[nvramiface]: ../share/org.chromium.tpmd.nvram.xml
+[impl]: implementation
+
+## Objective:
+Expose an API for using the TPM over DBus; pull all TPM-using code out of
+programs that currently use it (cryptohome and mount-encrypted).
+
+## Background:
+Right now, programs that want to use the TPM a) have to have access to talk to
+the TPM, and b) have to use the remarkably-arcane TPM API, which (among other
+flaws) is very difficult to mock out for testing.
+
+## Overview:
+Expose the TPM over DBus.
+
+## Detailed Design:
+The TPM daemon is a single program connected to DBus and the TPM, running as an
+otherwise-unprivileged user, providing an API for interacting with the TPM. It
+should require no privileges at all other than memory allocation and read/write,
+which makes it a good candidate for sandboxing.
+
+The TPM daemon will present itself as a single DBus object at path
+`/org/chromium/tpmd`, implementing two interfaces: the [`main interface`][iface]
+and the [`nvram interface`][nvramiface]. Some of these methods can take quite a
+while, so internally they are implemented asynchronously, but the exposed
+interface is synchronous as usual. You may need to increase your DBus timeout if
+making these calls synchronously, as some of them might be quite slow (tens of
+seconds).
+
+Internally, the main thread will handle DBus interaction, with a worker thread
+to make blocking calls into the TPM library. We'll use libbase's message loop
+functionality to wire this up.
diff --git a/doc/implementation.md b/doc/implementation.md
new file mode 100644
index 0000000..a2a8856
--- /dev/null
+++ b/doc/implementation.md
@@ -0,0 +1,20 @@
+# Implementation doc: tpmd
+
+[design]: design
+
+## Threads
+There are two threads: the dbus thread and the tpm thread. The dbus thread is
+responsible for handling incoming DBus method calls and posting them onto the
+TPM thread; it also handles the `GetStatus` API call and reads of TPM
+properties. We use dbus-c++ to generate adaptors and handle method calls. The
+flow when a method is called is this:
+
+* DBus method call arrives
+* Adaptor invoked on dbus thread
+* Adaptor posts task into tpm thread
+* The tpm thread eventually runs the function, which might block for a while
+* The tpm thread sends a reply to the method call over DBus
+
+To do this, we have to use dbus-c++ in its special 'async adaptor' mode, which
+makes it generate wrappers that can defer returning their results; we hand the
+required continuation (called a 'tag') to the tpm thread to use in its reply.
diff --git a/etc/org.chromium.tpmd.conf b/etc/org.chromium.tpmd.conf
new file mode 100644
index 0000000..995d251
--- /dev/null
+++ b/etc/org.chromium.tpmd.conf
@@ -0,0 +1,15 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy context="default">
+ <allow send_destination="org.chromium.tpmd"/>
+ </policy>
+
+ <policy user="tpmd">
+ <allow own="org.chromium.tpmd"/>
+ </policy>
+
+ <limit name="max_replies_per_connection">512</limit>
+</busconfig>
+
diff --git a/share/org.chromium.tpmd.nvram.xml b/share/org.chromium.tpmd.nvram.xml
index a4ce788..95f34e3 100644
--- a/share/org.chromium.tpmd.nvram.xml
+++ b/share/org.chromium.tpmd.nvram.xml
@@ -1,51 +1,114 @@
<node name="/" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.chromium.tpmd.nvram">
+ <method name="Allocate">
+ <tp:docstring>
+ Allocates an nvram at the specified index of the specified size. If
+ there is a n existing nvram in that slot, fails.
+ </tp:docstring>
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index at which to allocate the nvram object on the TPM.
+ </tp:docstring>
+ </arg>
+ <arg name="size" type="u" direction="in">
+ <tp:docstring>
+ Length of the allocated nvram, in bytes.
+ </tp:docstring>
+ </arg>
+ <arg name="options" type="a{sv}" direction="in">
+ <tp:docstring>
+ Extra options for this NVRAM area. Supported keys: "LockOnce", with a
+ boolean value.
+ </tp:docstring>
+ </arg>
+ </method>
<method name="Free">
<tp:docstring>
Frees this nvram region, destroying all data inside it.
</tp:docstring>
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
</method>
<method name="Write">
<tp:docstring>
Writes the supplied bytes to this nvram region. Fails if the region is
not writeable or too few or too many bytes are supplied.
</tp:docstring>
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
<arg name="bytes" type="ay" direction="in">
<tp:docstring>
Bytes to write.
</tp:docstring>
</arg>
- <arg name="result" type="b" direction="out">
- </arg>
</method>
<method name="Read">
<tp:docstring>
Reads bytes from this nvram region. Fails if the region is not allocated
or written.
</tp:docstring>
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
<arg name="bytes" type="ay" direction="out">
<tp:docstring>
The bytes read from this nvram region.
</tp:docstring>
</arg>
- <arg name="result" type="b" direction="out">
- </arg>
</method>
- <property name="IsDefined" type="b">
+ <method name="IsDefined">
<tp:docstring>
Has this nvram region been written?
</tp:docstring>
- </property>
- <property name="IsLocked" type="b">
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
+ <arg name="defined" type="b" direction="out">
+ <tp:docstring>
+ True if this region is defined.
+ </tp:docstring>
+ </arg>
+ </method>
+ <method name="IsLocked">
<tp:docstring>
Has this nvram region been locked? (i.e., can it no longer be written?)
</tp:docstring>
- </property>
- <property name="Size" type="u">
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
+ <arg name="locked" type="b" direction="out">
+ <tp:docstring>
+ True if this region is locked.
+ </tp:docstring>
+ </arg>
+ </method>
+ <method name="Size">
<tp:docstring>
The size of this nvram region, in bytes.
</tp:docstring>
- </property>
+ <arg name="index" type="u" direction="in">
+ <tp:docstring>
+ Index of this nvram region.
+ </tp:docstring>
+ </arg>
+ <arg name="size" type="u" direction="out">
+ <tp:docstring>
+ Size of this nvram region.
+ </tp:docstring>
+ </arg>
+ </method>
</interface>
</node>
diff --git a/share/org.chromium.tpmd.xml b/share/org.chromium.tpmd.xml
index 3c84959..9b0ef3d 100644
--- a/share/org.chromium.tpmd.xml
+++ b/share/org.chromium.tpmd.xml
@@ -2,49 +2,6 @@
<node name="/" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.chromium.tpmd">
- <method name="AllocateNvram">
- <tp:docstring>
- Allocates an nvram at the specified index of the specified size. If
- there is an existing nvram at the specified index, fails.
- </tp:docstring>
- <arg name="index" type="u" direction="in">
- <tp:docstring>
- Index at which to allocate the nvram object on the TPM.
- </tp:docstring>
- </arg>
- <arg name="size" type="u" direction="in">
- <tp:docstring>
- Length of the allocated nvram, in bytes.
- </tp:docstring>
- </arg>
- <arg name="options" type="a{sv}" direction="in">
- <tp:docstring>
- Extra options for this NVRAM area. Supported keys: "LockOnce", with a
- boolean value.
- </tp:docstring>
- </arg>
- <arg name="path" type="o" direction="out">
- <tp:docstring>
- The path to a DBus object representing the newly-allocated nvram slot.
- </tp:docstring>
- </arg>
- </method>
- <method name="GetNvram">
- <tp:docstring>
- Returns the path of a DBus object representing an existing nvram slot on
- the TPM.
- </tp:docstring>
- <arg name="index" type="u" direction="in">
- <tp:docstring>
- Index at which to allocate the nvram object on the TPM.
- </tp:docstring>
- </arg>
- <arg name="path" type="o" direction="out">
- <tp:docstring>
- The path to a DBus object representing the newly-allocated nvram slot.
- </tp:docstring>
- </arg>
- </method>
<method name="Encrypt">
<tp:docstring>
Encrypts a supplied plaintext with the tpmd sealing key, returning the
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..3091cb7
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,30 @@
+BASE_VER = 125070
+
+DBUSXX_XML2CPP ?= dbusxx-xml2cpp
+PKG_CONFIG ?= pkg-config
+PC_DEPS = dbus-1 dbus-c++-1 libchrome-$(BASE_VER) libchromeos-$(BASE_VER)
+PC_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PC_DEPS))
+PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))
+CXXFLAGS += $(PC_CFLAGS)
+BINDINGS := adaptors/org.chromium.tpmd.h adaptors/org.chromium.tpmd.nvram.h
+
+include common.mk
+
+CXXFLAGS += -fvisibility=hidden
+CXXFLAGS += -I$(OUT)
+
+all: CXX_BINARY(tpmd)
+
+OBJS := main.o tpmd.o volatile_nvram.o
+
+CXX_BINARY(tpmd): main.o $(OBJS)
+ $(call cxx_binary,$(PC_LIBS))
+clean: CXX_BINARY(tpmd)
+
+adaptors/%.h: ../share/%.xml
+ mkdir -p $(dir $@)
+ $(DBUSXX_XML2CPP) $^ --adaptor=$@ --adaptor-template=ef-adaptor-stubs.tpl
+
+main.o.depends: $(BINDINGS)
+tpmd.o.depends: $(BINDINGS)
+volatile_nvram.o.depends: $(BINDINGS)
diff --git a/src/common.mk b/src/common.mk
new file mode 100644
index 0000000..7f63f84
--- /dev/null
+++ b/src/common.mk
@@ -0,0 +1,799 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# If this file is part of another source distribution, it's license may be
+# stored in LICENSE.makefile or LICENSE.common.mk.
+#
+# NOTE NOTE NOTE
+# The authoritative common.mk is located in:
+# http://git.chromium.org/gitweb/?p=chromiumos/platform/common-mk.git
+# Please make all changes there, then copy into place in other repos.
+# NOTE NOTE NOTE
+#
+# This file provides a common architecture for building C/C++ source trees.
+# It uses recursive makefile inclusion to create a single make process which
+# can be built in the source tree or with the build artifacts placed elsewhere.
+#
+# It is fully parallelizable for all targets, including static archives.
+#
+# To use:
+# 1. Place common.mk in your top source level
+# 2. In your top-level Makefile, place "include common.mk" at the top
+# 3. In all subdirectories, create a 'module.mk' file that starts with:
+# include common.mk
+# And then contains the remainder of your targets.
+# 4. All build targets should look like:
+# relative/path/target: relative/path/obj.o
+#
+# See existing makefiles for rule examples.
+#
+# Exported macros:
+# - cc_binary, cxx_binary provide standard compilation steps for binaries
+# - cxx_library, cc_library provide standard compilation steps for
+# shared objects.
+# All of the above optionally take an argument for extra flags.
+# - update_archive creates/updates a given .a target
+#
+# Instead of using the build macros, most users can just use wrapped targets:
+# - CXX_BINARY, CC_BINARY, CC_STATIC_BINARY, CXX_STATIC_BINARY
+# - CXX_LIBRARY, CC_LIBRARY, CC_STATIC_LIBRARY, CXX_STATIC_LIBRARY
+# - E.g., CXX_BINARY(mahbinary): foo.o
+# - object.depends targets may be used when a prerequisite is required for an
+# object file. Because object files result in multiple build artifacts to
+# handle PIC and PIE weirdness. E.g.
+# foo.o.depends: generated/dbus.h
+# - TEST(binary) or TEST(CXX_BINARY(binary)) may be used as a prerequisite
+# for the tests target to trigger an automated test run.
+# - CLEAN(file_or_dir) dependency can be added to 'clean'.
+#
+# If source code is being generated, rules will need to be registered for
+# compiling the objects. This can be done by adding one of the following
+# to the Makefile:
+# - For C source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/gen_b.o,CC,c))
+# - For C++ source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/gen_b.o,CXX,cc))
+#
+# Exported targets meant to have prerequisites added to:
+# - all - Your desired targets should be given
+# - tests - Any TEST(test_binary) targets should be given
+# - FORCE - force the given target to run regardless of changes
+# In most cases, using .PHONY is preferred.
+#
+# Possible command line variables:
+# - COLOR=[0|1] to set ANSI color output (default: 1)
+# - VERBOSE=[0|1] to hide/show commands (default: 0)
+# - MODE=dbg to turn down optimizations (default: opt)
+# - ARCH=[x86|arm|supported qemu name] (default: from portage or uname -m)
+# - SPLITDEBUG=[0|1] splits debug info in target.debug (default: 0)
+# If NOSTRIP=1, SPLITDEBUG will never strip the final emitted objects.
+# - NOSTRIP=[0|1] determines if binaries are stripped. (default: 1)
+# NOSTRIP=0 and MODE=opt will also drop -g from the CFLAGS.
+# - VALGRIND=[0|1] runs tests under valgrind (default: 0)
+# - OUT=/path/to/builddir puts all output in given path (default: $PWD)
+# - VALGRIND_ARGS="" supplies extra memcheck arguments
+#
+# Per-target(-ish) variable:
+# - NEEDS_ROOT=[0|1] allows a TEST() target to run with root.
+# Default is 0 unless it is running under QEmu.
+# - NEEDS_MOUNTS=[0|1] allows a TEST() target running on QEmu to get
+# setup mounts in the $(SYSROOT)
+#
+# Caveats:
+# - Directories or files with spaces in them DO NOT get along with GNU Make.
+# If you need them, all uses of dir/notdir/etc will need to have magic
+# wrappers. Proceed at risk to your own sanity.
+# - External CXXFLAGS and CFLAGS should be passed via the environment since
+# this file does not use 'override' to control them.
+# - Our version of GNU Make doesn't seem to support the 'private' variable
+# annotation, so you can't tag a variable private on a wrapping target.
+
+# Behavior configuration variables
+SPLITDEBUG ?= 0
+NOSTRIP ?= 1
+VALGRIND ?= 0
+COLOR ?= 1
+VERBOSE ?= 0
+MODE ?= opt
+ARCH ?= $(shell uname -m)
+# TODO: profiling support not completed.
+PROFILING ?= 0
+NEEDS_ROOT = 0
+NEEDS_MOUNTS = 0
+
+# Put objects in a separate tree based on makefile locations
+# This means you can build a tree without touching it:
+# make -C $SRCDIR # will create ./build-$(MODE)
+# Or
+# make -C $SRCDIR OUT=$PWD
+# This variable is extended on subdir calls and doesn't need to be re-called.
+OUT ?= $(PWD)/
+
+# Make OUT now so we can use realpath.
+$(shell mkdir -p "$(OUT)")
+
+# TODO(wad) Relative paths are resolved against SRC and not the calling dir.
+# Ensure a command-line supplied OUT has a slash
+override OUT := $(realpath $(OUT))/
+
+# SRC is not meant to be set by the end user, but during make call relocation.
+# $(PWD) != $(CURDIR) all the time.
+export SRC ?= $(CURDIR)
+
+# Re-start in the $(OUT) directory if we're not there.
+# We may be invoked using -C or bare and we need to ensure behavior
+# is consistent so we check both PWD vs OUT and PWD vs CURDIR.
+override RELOCATE_BUILD := 0
+ifneq (${PWD}/,${OUT})
+override RELOCATE_BUILD := 1
+endif
+# Make sure we're running with no builtin targets. They cause
+# leakage and mayhem!
+ifneq (${PWD},${CURDIR})
+override RELOCATE_BUILD := 1
+# If we're run from the build dir, don't let it get cleaned up later.
+ifeq (${PWD}/,${OUT})
+$(shell touch "$(PWD)/.dont_delete_on_clean")
+endif
+endif # ifneq (${PWD},${CURDIR}
+
+# "Relocate" if we need to restart without implicit rules.
+ifeq ($(subst r,,$(MAKEFLAGS)),$(MAKEFLAGS))
+override RELOCATE_BUILD := 1
+endif
+
+ifeq (${RELOCATE_BUILD},1)
+# By default, silence build output. Reused below as well.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+# This target will override all targets, including prerequisites. To avoid
+# calling $(MAKE) once per prereq on the given CMDGOAL, we guard it with a local
+# variable.
+RUN_ONCE := 0
+MAKECMDGOALS ?= all
+# Keep the rules split as newer make does not allow them to be declared
+# on the same line. But the way :: rules work, the _all here will also
+# invoke the %:: rule while retaining "_all" as the default.
+_all::
+%::
+ $(if $(filter 0,$(RUN_ONCE)), \
+ $(QUIET)mkdir -p "$(OUT)" && \
+ cd $(OUT) && \
+ $(MAKE) -r -I "$(SRC)" -f "$(CURDIR)/Makefile" \
+ SRC="$(CURDIR)" OUT="$(OUT)" $(foreach g,$(MAKECMDGOALS),"$(g)"),)
+ $(eval RUN_ONCE := 1)
+pass-to-subcall := 1
+endif
+
+ifeq ($(pass-to-subcall),)
+
+# Only call MODULE if we're in a submodule
+MODULES_LIST := $(filter-out Makefile %.d,$(MAKEFILE_LIST))
+ifeq ($(words $(filter-out Makefile common.mk %.d $(SRC)/Makefile \
+ $(SRC)/common.mk,$(MAKEFILE_LIST))),0)
+
+# All the top-level defines outside of module.mk.
+
+#
+# Helper macros
+#
+
+# Creates the actual archive with an index.
+# The target $@ must end with .pic.a or .pie.a.
+define update_archive
+ $(QUIET)mkdir -p "$(dir $(TARGET_OR_MEMBER))"
+ $(QUIET)# Create the archive in one step to avoid parallel use accessing it
+ $(QUIET)# before all the symbols are present.
+ @$(ECHO) "AR $(subst \
+$(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o)) \
+-> $(subst $(SRC)/,,$(TARGET_OR_MEMBER))"
+ $(QUIET)$(AR) rcs $(TARGET_OR_MEMBER) \
+ $(subst $(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_binary
+ $(call COMPILE_BINARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+define cxx_binary
+ $(call COMPILE_BINARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_library
+ $(call COMPILE_LIBRARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+define cxx_library
+ $(call COMPILE_LIBRARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Deletes files silently if they exist. Meant for use in any local
+# clean targets.
+define silent_rm
+ $(if $(wildcard $(1)),
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANFILE$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RM) $(1) 2>/dev/null) || true,)
+endef
+define silent_rmdir
+ $(if $(wildcard $(1)),
+ $(if $(wildcard $(1)/*),
+ $(QUIET)# $(1) not empty [$(wildcard $(1)/*)]. Not deleting.,
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANDIR$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RMDIR) $(1) 2>/dev/null) || true),)
+endef
+
+#
+# Default variable values
+#
+
+OBJCOPY ?= objcopy
+STRIP ?= strip
+RMDIR ?= rmdir
+# Only override CC and CXX if they are from make.
+ifeq ($(origin CC), default)
+ CC = gcc
+endif
+ifeq ($(origin CXX), default)
+ CXX = g++
+endif
+ifeq ($(origin RANLIB), default)
+ RANLIB = ranlib
+endif
+RANLIB ?= ranlib
+ECHO = /bin/echo -e
+
+ifeq ($(PROFILING),1)
+ $(warning PROFILING=1 disables relocatable executables.)
+endif
+
+# To update these from an including Makefile:
+# CXXFLAGS += -mahflag # Append to the list
+# CXXFLAGS := -mahflag $(CXXFLAGS) # Prepend to the list
+# CXXFLAGS := $(filter-out badflag,$(CXXFLAGS)) # Filter out a value
+# The same goes for CFLAGS.
+# TODO(wad) Moving to -fvisibility=internal by default would be nice too.
+CXXFLAGS := $(CXXFLAGS) -Wall -Werror -fstack-protector-all \
+ -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -ggdb3 -Wa,--noexecstack -O1 \
+ -fvisibility=internal -Wformat=2
+CFLAGS := $(CFLAGS) -Wall -Werror -fstack-protector-all -fno-strict-aliasing \
+ -DFORTIFY_SOURCE=2 -ggdb3 -Wa,--noexecstack -O1 -fvisibility=internal \
+ -Wformat=2
+
+ifeq ($(PROFILING),1)
+ CFLAGS := -pg
+ CXXFLAGS := -pg
+endif
+
+ifeq ($(MODE),opt)
+ # Up the optimizations.
+ CFLAGS := $(filter-out -O1,$(CFLAGS)) -O2
+ CXXFLAGS := $(filter-out -O1,$(CXXFLAGS)) -O2
+ # Only drop -g* if symbols aren't desired.
+ ifeq ($(NOSTRIP),0)
+ # TODO: do we want -fomit-frame-pointer on x86?
+ CFLAGS := $(filter-out -ggdb3,$(CFLAGS))
+ CXXFLAGS := $(filter-out -ggdb3,$(CXXFLAGS))
+ endif
+endif
+
+LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now
+
+# Fancy helpers for color if a prompt is defined
+ifeq ($(COLOR),1)
+COLOR_RESET = \x1b[0m
+COLOR_GREEN = \x1b[32;01m
+COLOR_RED = \x1b[31;01m
+COLOR_YELLOW = \x1b[33;01m
+endif
+
+# By default, silence build output.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+#
+# Implementation macros for compile helpers above
+#
+
+# Useful for dealing with pie-broken toolchains.
+# Call make with PIE=0 to disable default PIE use.
+OBJ_PIE_FLAG = -fPIE
+COMPILE_PIE_FLAG = -pie
+ifeq ($(PIE),0)
+ OBJ_PIE_FLAG =
+ COMPILE_PIE_FLAG =
+endif
+
+# Favor member targets first for CXX_BINARY(%) magic.
+# And strip out nested members if possible.
+LP := (
+RP := )
+TARGET_OR_MEMBER = $(lastword $(subst $(LP), ,$(subst $(RP),,$(or $%,$@))))
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o files.
+define COMPILE_BINARY_implementation
+ @$(ECHO) "LD$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(QUIET)mkdir -p "$(dir $(TARGET_OR_MEMBER))"
+ $(QUIET)$($(1)) $(COMPILE_PIE_FLAGS) -o $(TARGET_OR_MEMBER) \
+ $(filter %.o %.a,$(^:.o=.pie.o)) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(2) $(LDFLAGS)
+ $(call conditional_strip)
+ @$(ECHO) -n "BIN "
+ @$(ECHO) "$(COLOR_GREEN)$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+# TODO: add version support extracted from PV environment variable
+#ifeq ($(PV),9999)
+#$(warning PV=$(PV). If shared object versions matter, please force PV=.)
+#endif
+# Then add -Wl,-soname,$@.$(PV) ?
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o values. (Remember to add -L$(OUT) -llib)
+COMMA := ,
+define COMPILE_LIBRARY_implementation
+ @$(ECHO) "SHARED$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(QUIET)mkdir -p "$(dir $(TARGET_OR_MEMBER))"
+ $(QUIET)$($(1)) -shared -Wl,-E -o $(TARGET_OR_MEMBER) \
+ $(if $(filter %.a,$^),-Wl$(COMMA)--whole-archive,) \
+ $(filter %.o ,$(^:.o=.pic.o)) \
+ $(foreach a,$(filter %.a,$^),-L$(dir $(a)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(a))))) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(2) $(LDFLAGS)
+ $(call conditional_strip)
+ @$(ECHO) -n "LIB $(COLOR_GREEN)"
+ @$(ECHO) "$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+define conditional_strip
+ $(if $(filter 0,$(NOSTRIP)),$(call strip_artifact))
+endef
+
+define strip_artifact
+ @$(ECHO) "STRIP $(subst $(OUT)/,,$(TARGET_OR_MEMBER))"
+ $(if $(filter 1,$(SPLITDEBUG)), @$(ECHO) -n "DEBUG "; \
+ $(ECHO) "$(COLOR_YELLOW)\
+$(subst $(OUT)/,,$(TARGET_OR_MEMBER)).debug$(COLOR_RESET)")
+ $(if $(filter 1,$(SPLITDEBUG)), \
+ $(QUIET)$(OBJCOPY) --only-keep-debug "$(TARGET_OR_MEMBER)" \
+ "$(TARGET_OR_MEMBER).debug")
+ $(if $(filter-out dbg,$(MODE)),$(QUIET)$(STRIP) --strip-unneeded \
+ "$(TARGET_OR_MEMBER)",)
+endef
+
+#
+# Global pattern rules
+#
+
+# Below, the archive member syntax is abused to create fancier
+# syntactic sugar for recipe authors that avoids needed to know
+# subcall options. The downside is that make attempts to look
+# into the phony archives for timestamps. This will cause the final
+# target to be rebuilt/linked on _every_ call to make even when nothing
+# has changed. Until a better way presents itself, we have helpers that
+# do the stat check on make's behalf. Dodgy but simple.
+define old_or_no_timestamp
+ $(if $(realpath $%),,$(1))
+ $(if $(shell find $^ -cnewer "$%" 2>/dev/null),$(1))
+endef
+
+define check_deps
+ $(if $(filter 0,$(words $^)),\
+ $(error Missing dependencies or declaration of $@($%)),)
+endef
+
+# Build a cxx target magically
+CXX_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary))
+clean: CLEAN(CXX_BINARY*)
+
+CC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary))
+clean: CLEAN(CC_BINARY*)
+
+CXX_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary,-static))
+clean: CLEAN(CXX_STATIC_BINARY*)
+
+CC_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary,-static))
+clean: CLEAN(CC_STATIC_BINARY*)
+
+CXX_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_library))
+clean: CLEAN(CXX_LIBRARY*)
+
+CXX_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_library))
+clean: CLEAN(CC_LIBRARY*)
+
+CC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CXX_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CXX_STATIC_LIBRARY*)
+
+CXX_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CC_STATIC_LIBRARY*)
+
+CC_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+
+TEST(%): % qemu_chroot_install
+ $(call TEST_implementation)
+.PHONY: TEST
+
+# multiple targets with a wildcard need to share an directory.
+# Don't use this directly it just makes sure the directory is removed _after_
+# the files are.
+CLEANFILE(%):
+ $(call silent_rm,$(TARGET_OR_MEMBER))
+.PHONY: CLEANFILE
+
+CLEAN(%): CLEANFILE(%)
+ $(QUIET)# CLEAN($%) meta-target called
+ $(if $(filter-out $(PWD)/,$(dir $(abspath $(TARGET_OR_MEMBER)))), \
+ $(call silent_rmdir,$(dir $(abspath $(TARGET_OR_MEMBER)))),\
+ $(QUIET)# Not deleting $(dir $(abspath $(TARGET_OR_MEMBER))) yet.)
+.PHONY: CLEAN
+
+#
+# Top-level objects and pattern rules
+#
+
+# All objects for .c files at the top level
+C_OBJECTS = $(patsubst $(SRC)/%.c,%.o,$(wildcard $(SRC)/*.c))
+
+
+# All objects for .cxx files at the top level
+CXX_OBJECTS = $(patsubst $(SRC)/%.cc,%.o,$(wildcard $(SRC)/*.cc))
+
+# Note, the catch-all pattern rules don't work in subdirectories because
+# we're building from the $(OUT) directory. At the top-level (here) they will
+# work, but we go ahead and match using the module form. Then we can place a
+# generic pattern rule to capture leakage from the main Makefile. (Later in the
+# file.)
+#
+# The reason target specific pattern rules work well for modules,
+# MODULE_C_OBJECTS, is because it scopes the behavior to the given target which
+# ensures we get a relative directory offset from $(OUT) which otherwise would
+# not match without further magic on a per-subdirectory basis.
+
+# Creates object file rules. Call with eval.
+# $(1) list of .o files
+# $(2) source type (CC or CXX)
+# $(3) source suffix (cc or c)
+# $(4) source dir: _only_ if $(SRC). Leave blank for obj tree.
+define add_object_rules
+$(patsubst %.o,%.pie.o,$(1)): %.pie.o: $(4)%.$(3) %.o.depends
+ $$(QUIET)mkdir -p "$$(dir $$@)"
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$(CXXFLAGS) $$(OBJ_PIE_FLAG))
+
+$(patsubst %.o,%.pic.o,$(1)): %.pic.o: $(4)%.$(3) %.o.depends
+ $$(QUIET)mkdir -p "$$(dir $$@)"
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$(CXXFLAGS) -fPIC)
+
+# Placeholder for depends
+$(patsubst %.o,%.o.depends,$(1)):
+ $$(QUIET)mkdir -p "$$(dir $$@)"
+ $$(QUIET)touch "$$@"
+
+$(1): %.o: %.pic.o %.pie.o
+ $$(QUIET)mkdir -p "$$(dir $$@)"
+ $$(QUIET)touch "$$@"
+endef
+
+define OBJECT_PATTERN_implementation
+ @$(ECHO) "$(1) $(subst $(SRC)/,,$<) -> $(2).o"
+ $(QUIET)mkdir -p "$(dir $(2))"
+ $(QUIET)$($(1)) -c -MD -MF $(2).d $(3) -o $(2).o $<
+ $(QUIET)# Wrap all the deps in $(wildcard) so a missing header
+ $(QUIET)# won't cause weirdness. First we remove newlines and \,
+ $(QUIET)# then wrap it.
+ $(QUIET)sed -i -e :j -e '$$!N;s|\\\s*\n| |;tj' \
+ -e 's|^\(.*\s*:\s*\)\(.*\)$$|\1 $$\(wildcard \2\)|' $(2).d
+endef
+
+# Now actually register handlers for C(XX)_OBJECTS.
+$(eval $(call add_object_rules,$(C_OBJECTS),CC,c,$(SRC)/))
+$(eval $(call add_object_rules,$(CXX_OBJECTS),CXX,cc,$(SRC)/))
+
+# Disable default pattern rules to help avoid leakage.
+# These may already be handled by '-r', but let's keep it to be safe.
+%: %.o ;
+%.a: %.o ;
+%.o: %.c ;
+%.o: %.cc ;
+
+# NOTE: A specific rule for archive objects is avoided because parallel
+# update of the archive causes build flakiness.
+# Instead, just make the objects the prerequisites and use update_archive
+# To use the foo.a(obj.o) functionality, targets would need to specify the
+# explicit object they expect on the prerequisite line.
+
+#
+# Architecture detection and QEMU wrapping
+#
+
+HOST_ARCH ?= $(shell uname -m)
+override ARCH := $(strip $(ARCH))
+override HOST_ARCH := $(strip $(HOST_ARCH))
+# emake will supply "x86" or "arm" for ARCH, but
+# if uname -m runs and you get x86_64, then this subst
+# will break.
+ifeq ($(subst x86,i386,$(ARCH)),i386)
+ QEMU_ARCH := $(subst x86,i386,$(ARCH)) # x86 -> i386
+else ifeq ($(subst amd64,x86_64,$(ARCH)),x86_64)
+ QEMU_ARCH := $(subst amd64,x86_64,$(ARCH)) # amd64 -> x86_64
+else
+ QEMU_ARCH = $(ARCH)
+endif
+override QEMU_ARCH := $(strip $(QEMU_ARCH))
+
+# If we're cross-compiling, try to use qemu for running the tests.
+ifneq ($(QEMU_ARCH),$(HOST_ARCH))
+ ifeq ($(SYSROOT),)
+ $(info SYSROOT not defined. qemu-based testing disabled)
+ else
+ # A SYSROOT is assumed for QEmu use.
+ USE_QEMU ?= 1
+
+ # Allow 64-bit hosts to run 32-bit without qemu.
+ ifeq ($(HOST_ARCH),x86_64)
+ ifeq ($(QEMU_ARCH),i386)
+ USE_QEMU = 0
+ endif
+ endif
+ endif
+else
+ USE_QEMU ?= 0
+endif
+
+SYSROOT_OUT = $(OUT)
+ifneq ($(SYSROOT),)
+ SYSROOT_OUT = $(subst $(SYSROOT),,$(OUT))
+else
+ # Default to / when all the empty-sysroot logic is done.
+ SYSROOT = /
+endif
+
+
+#
+# Output full configuration at top level
+#
+
+# Don't show on clean
+ifneq ($(MAKECMDGOALS),clean)
+ $(info build configuration:)
+ $(info - OUT=$(OUT))
+ $(info - SRC=$(SRC))
+ $(info - MODE=$(MODE))
+ $(info - SPLITDEBUG=$(SPLITDEBUG))
+ $(info - NOSTRIP=$(NOSTRIP))
+ $(info - VALGRIND=$(VALGRIND))
+ $(info - COLOR=$(COLOR))
+ $(info - ARCH=$(ARCH))
+ $(info - QEMU_ARCH=$(QEMU_ARCH))
+ $(info - SYSROOT=$(SYSROOT))
+ $(info )
+endif
+
+#
+# Standard targets with detection for when they are improperly configured.
+#
+
+# all does not include tests by default
+all:
+ $(QUIET)(test -z "$^" && \
+ $(ECHO) "You must add your targets as 'all' prerequisites") || true
+ $(QUIET)test -n "$^"
+
+# Builds and runs tests for the target arch
+# Run them in parallel
+tests:
+.PHONY: tests
+
+qemu_clean:
+ $(call if_qemu,$(call silent_rm,$(OUT)/qemu-$(QEMU_ARCH)))
+
+qemu_chroot_install:
+ifeq ($(USE_QEMU),1)
+ $(QUIET)$(ECHO) "QEMU Preparing qemu-$(QEMU_ARCH)"
+ $(QUIET)cp -fu /usr/bin/qemu-$(QEMU_ARCH) $(OUT)/qemu-$(QEMU_ARCH)
+ $(QUIET)chmod a+rx $(OUT)/qemu-$(QEMU_ARCH)
+endif
+.PHONY: qemu_clean qemu_chroot_install
+
+# TODO(wad) Move to -L $(SYSROOT) and fakechroot when qemu-user
+# doesn't hang traversing /proc from SYSROOT.
+QEMU_CMD =
+ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo , )
+MOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) mount, \#)
+UMOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) umount, \#)
+QEMU_LDPATH = $(SYSROOT_LDPATH):/lib64:/lib:/usr/lib64:/usr/lib
+ROOT_CMD_LDPATH = $(SYSROOT_LDPATH):$(SYSROOT)/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/lib:$(SYSROOT)/usr/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/usr/lib
+ifeq ($(USE_QEMU),1)
+ export QEMU_CMD = \
+ sudo chroot $(SYSROOT) $(SYSROOT_OUT)qemu-$(QEMU_ARCH) \
+ -drop-ld-preload \
+ -E LD_LIBRARY_PATH="$(QEMU_LDPATH):$(patsubst $(OUT),,$(LD_DIRS))" \
+ -E HOME="$(HOME)" --
+ # USE_QEMU conditional function
+ define if_qemu
+ $(1)
+ endef
+else
+ ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo, ) \
+ LD_LIBRARY_PATH="$(ROOT_CMD_LDPATH):$(LD_DIRS)"
+ define if_qemu
+ $(2)
+ endef
+endif
+
+VALGRIND_CMD =
+ifeq ($(VALGRIND),1)
+ VALGRIND_CMD = /usr/bin/valgrind --tool=memcheck $(VALGRIND_ARGS) --
+endif
+
+define TEST_implementation
+ $(QUIET)$(call TEST_setup)
+ $(QUIET)$(call TEST_run)
+ $(QUIET)$(call TEST_teardown)
+ $(QUIET)exit $$(cat $(OUT)$(TARGET_OR_MEMBER).status.test)
+endef
+
+define TEST_setup
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)SETUP$(COLOR_RESET)]"
+ $(QUIET)# Setup a target-specific results file
+ $(QUIET)(echo 1 > $(OUT)$(TARGET_OR_MEMBER).status.test)
+ $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).cleanup.test)
+ $(QUIET)# No setup if we are not using QEMU
+ $(QUIET)# TODO(wad) this is racy until we use a vfs namespace
+ $(call if_qemu,\
+ $(QUIET)sudo mkdir -p "$(SYSROOT)/proc" "$(SYSROOT)/dev")
+ $(call if_qemu,\
+ $(QUIET)$(MOUNT_CMD) --bind /proc "$(SYSROOT)/proc")
+ $(call if_qemu,\
+ $(QUIET)$(MOUNT_CMD) --bind /dev "$(SYSROOT)/dev")
+ $(call if_qemu,\
+ $(QUIET)(echo "$(UMOUNT_CMD) -l '$(SYSROOT)/proc'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).cleanup.test"))
+ $(call if_qemu,\
+ $(QUIET)(echo "$(UMOUNT_CMD) -l '$(SYSROOT)/dev'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).cleanup.test"))
+endef
+
+define TEST_teardown
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)TEARDOWN$(COLOR_RESET)]"
+ $(call if_qemu, $(QUIET)$(SHELL) "$(OUT)$(TARGET_OR_MEMBER).cleanup.test")
+endef
+
+# Use GTEST_ARGS.[arch] if defined.
+override GTEST_ARGS.real = \
+ $(call if_qemu,$(GTEST_ARGS.qemu.$(QEMU_ARCH)),$(GTEST_ARGS.host.$(HOST_ARCH)))
+
+define TEST_run
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_GREEN)RUN$(COLOR_RESET)]"
+ $(QUIET)(echo 1 > "$(OUT)$(TARGET_OR_MEMBER).status.test")
+ -($(ROOT_CMD) $(QEMU_CMD) $(VALGRIND_CMD) \
+ "$(strip $(call if_qemu, $(SYSROOT_OUT),$(OUT))$(TARGET_OR_MEMBER))" \
+ $(if $(filter-out 0,$(words $(GTEST_ARGS.real))),$(GTEST_ARGS.real),\
+ $(GTEST_ARGS)) && \
+ echo 0 > "$(OUT)$(TARGET_OR_MEMBER).status.test")
+endef
+
+# Recursive list reversal so that we get RMDIR_ON_CLEAN in reverse order.
+define reverse
+$(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
+endef
+
+clean: qemu_clean
+clean: CLEAN($(OUT)*.d) CLEAN($(OUT)*.o) CLEAN($(OUT)*.debug)
+clean: CLEAN($(OUT)*.test) CLEAN($(OUT)*.depends)
+
+clean:
+ $(QUIET)# Always delete the containing directory last.
+ $(call silent_rmdir,$(OUT))
+
+FORCE: ;
+# Empty rule for use when no special targets are needed, like large_tests
+NONE:
+
+.PHONY: clean NONE valgrind NONE
+.DEFAULT_GOAL := all
+# Don't let make blow away "intermediates"
+.PRECIOUS: %.pic.o %.pie.o %.a %.pic.a %.pie.a %.test
+
+# Start accruing build info
+OUT_DIRS = $(OUT)
+LD_DIRS = $(OUT)
+SRC_DIRS = $(SRC)
+
+include $(wildcard $(OUT)*.d)
+SUBMODULE_DIRS = $(wildcard $(SRC)/*/module.mk)
+include $(SUBMODULE_DIRS)
+
+
+else ## In duplicate inclusions of common.mk
+
+# Get the current inclusion directory without a trailing slash
+MODULE := $(patsubst %/,%, \
+ $(dir $(lastword $(filter-out %common.mk,$(MAKEFILE_LIST)))))
+MODULE := $(subst $(SRC)/,,$(MODULE))
+MODULE_NAME := $(subst /,_,$(MODULE))
+#VPATH := $(MODULE):$(VPATH)
+
+
+# Depth first
+$(eval OUT_DIRS += $(OUT)$(MODULE))
+$(eval SRC_DIRS += $(OUT)$(MODULE))
+$(eval LD_DIRS := $(LD_DIRS):$(OUT)$(MODULE))
+
+# Add the defaults from this dir to rm_clean
+clean: CLEAN($(OUT)$(MODULE)/*.d) CLEAN($(OUT)$(MODULE)/*.o)
+clean: CLEAN($(OUT)$(MODULE)/*.debug) CLEAN($(OUT)$(MODULE)/*.test)
+clean: CLEAN($(OUT)$(MODULE)/*.depends)
+
+$(info + submodule: $(MODULE_NAME))
+# We must eval otherwise they may be dropped.
+MODULE_C_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.c,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.c))
+$(eval $(MODULE_NAME)_C_OBJECTS ?= $(MODULE_C_OBJECTS))
+MODULE_CXX_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.cc,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.cc))
+$(eval $(MODULE_NAME)_CXX_OBJECTS ?= $(MODULE_CXX_OBJECTS))
+
+# Note, $(MODULE) is implicit in the path to the %.c.
+# See $(C_OBJECTS) for more details.
+# Register rules for the module objects.
+$(eval $(call add_object_rules,$(MODULE_C_OBJECTS),CC,c,$(SRC)/))
+$(eval $(call add_object_rules,$(MODULE_CXX_OBJECTS),CXX,cc,$(SRC)/))
+
+# Continue recursive inclusion of module.mk files
+SUBMODULE_DIRS = $(wildcard $(SRC)/$(MODULE)/*/module.mk)
+include $(wildcard $(OUT)$(MODULE)/*.d)
+include $(SUBMODULE_DIRS)
+
+endif
+endif ## pass-to-subcall wrapper for relocating the call directory
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..4692804
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdlib.h>
+
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <chromeos/syslog_logging.h>
+
+#include "tpmd.h"
+
+#ifndef VCSID
+#define VCSID "<not set>"
+#endif
+
+void start() {
+ DBus::BusDispatcher dispatcher;
+ DBus::default_dispatcher = &dispatcher;
+ DBus::Connection conn = DBus::Connection::SystemBus();
+ tpmd::Tpmd tpmd(&conn, &dispatcher);
+ if (!tpmd.Init())
+ exit(1);
+ tpmd.Run();
+}
+
+int main(int argc, char *argv[]) {
+ CommandLine::Init(argc, argv);
+ chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogToStderr);
+ LOG(INFO) << "tpmd " << VCSID << " starting";
+ start();
+ return 0;
+}
diff --git a/src/nvram.h b/src/nvram.h
new file mode 100644
index 0000000..f65d765
--- /dev/null
+++ b/src/nvram.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NVRAM_H_
+#define NVRAM_H_
+
+#include <stdint.h>
+#include <cstring>
+#include <vector>
+
+namespace tpmd {
+
+class Nvram {
+ public:
+ typedef std::vector<uint8_t> Bytes;
+
+ enum {
+ // If this flag is supplied to Allocate(), this region will be locked once
+ // written.
+ NVRAM_LOCKONCE = 0x00000001,
+ };
+
+ // Drops this reference to the backing Nvram slot. Note that this does _not_
+ // deallocate the underlying Nvram slot, but it _does_ destroy this object's
+ // reference to it. If you need to deallocate the slot, use Free() below.
+ virtual ~Nvram() { }
+
+ // Allocates a new Nvram slot and makes this object a reference to it. Returns
+ // true for success.
+ virtual bool Allocate(uint32_t slot, size_t len, uint32_t flags) = 0;
+
+ // Frees the underlying Nvram slot. Does not delete this object, but destroys
+ // the reference to the slot. Returns true for success, in which case the
+ // reference is destroyed; returns false for failure, in which case the
+ // reference is intact.
+ virtual bool Free(uint32_t slot) = 0;
+
+ // Writes the supplied bytes to this Nvram slot. The length of |bytes| must be
+ // the same as the size of this Nvram slot. Returns true for success.
+ virtual bool Write(uint32_t slot, const Bytes& bytes) = 0;
+
+ // Reads the contents of this Nvram slot. Returns true for success, in which
+ // case |bytes| is modified; returns false for failure, in which case |bytes|
+ // is untouched.
+ virtual bool Read(uint32_t slot, Bytes* bytes) = 0;
+
+ // Returns whether this region has ever been written.
+ virtual bool is_defined(uint32_t slot) = 0;
+
+ // Returns whether this region is locked, preventing writing.
+ virtual bool is_locked(uint32_t slot) = 0;
+
+ // Returns the size of this region, in bytes.
+ virtual size_t size(uint32_t slot) = 0;
+
+ // Returns how many slots are available.
+ virtual size_t num_slots() = 0;
+};
+
+} // namespace tpmd
+
+#endif /* !NVRAM_H_ */
diff --git a/src/tpmd.cc b/src/tpmd.cc
new file mode 100644
index 0000000..877f584
--- /dev/null
+++ b/src/tpmd.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "nvram.h"
+#include "tpmd.h"
+#include "volatile_nvram.h"
+
+namespace tpmd {
+
+static const char* kTpmdService = "org.chromium.tpmd";
+static const char* kTpmdPath = "/org/chromium/tpmd";
+static const char* kNvramError = "org.chromium.tpmd.nvram.error";
+
+Tpmd::Tpmd(DBus::Connection* connection, DBus::BusDispatcher* dispatcher)
+ : DBus::ObjectAdaptor(*connection, kTpmdPath, REGISTER_NOW,
+ AVOID_EXCEPTIONS),
+ dbus_(connection), dispatcher_(dispatcher),
+ nvram_(new VolatileNvram()) { }
+
+Tpmd::~Tpmd() { }
+
+bool Tpmd::Init() {
+ try {
+ dbus_->request_name(kTpmdService);
+ } catch (DBus::Error e) { // NOLINT dbuscxx
+ LOG(ERROR) << "can't acquire name: " << e.what()
+ << " " << e.name() << " " << e.message();
+ return false;
+ }
+ return true;
+}
+
+void Tpmd::Run() {
+ dispatcher_->enter();
+ while (1)
+ dispatcher_->do_iteration();
+ // unreachable
+ dispatcher_->leave();
+}
+
+DBus::Message Tpmd::Encrypt(const DBus::CallMessage& call,
+ const Bytes& plaintext) {
+ return EncryptMakeReply(call, plaintext);
+}
+
+DBus::Message Tpmd::Decrypt(const DBus::CallMessage& call,
+ const Bytes& ciphertext) {
+ return DecryptMakeReply(call, ciphertext);
+}
+
+DBus::Message Tpmd::GetStatus(const DBus::CallMessage& call) {
+ return GetStatusMakeReply(call, "{}");
+}
+
+DBus::Message Tpmd::Allocate(const DBus::CallMessage& call,
+ const uint32_t& slot, const uint32_t& size,
+ const VariantMap& opts) {
+ uint32_t flags = 0;
+ if (opts.count("LockOnce") && opts.find("LockOnce")->second)
+ flags |= Nvram::NVRAM_LOCKONCE;
+ if (!nvram_->Allocate(slot, size, flags))
+ return DBus::ErrorMessage(call, kNvramError, "Allocate() failed");
+ return AllocateMakeReply(call);
+}
+
+DBus::Message Tpmd::Free(const DBus::CallMessage& call, const uint32_t& slot) {
+ if (!nvram_->Free(slot))
+ return DBus::ErrorMessage(call, kNvramError, "Free() failed");
+ return FreeMakeReply(call);
+}
+
+DBus::Message Tpmd::Write(const DBus::CallMessage& call, const uint32_t& slot,
+ const Bytes& bytes) {
+ if (bytes.size() != nvram_->size(slot)) {
+ std::string errstr = StringPrintf("Write(%u) size mismatch: %u != %u",
+ slot, bytes.size(), nvram_->size(slot));
+ return DBus::ErrorMessage(call, kNvramError, errstr.c_str());
+ }
+ if (!nvram_->Write(slot, bytes))
+ return DBus::ErrorMessage(call, kNvramError, "Write() failed");
+ return WriteMakeReply(call);
+}
+
+DBus::Message Tpmd::Read(const DBus::CallMessage& call, const uint32_t& slot) {
+ Bytes data;
+ if (!nvram_->Read(slot, &data))
+ return DBus::ErrorMessage(call, kNvramError, "Read() failed");
+ return ReadMakeReply(call, data);
+}
+
+DBus::Message Tpmd::IsDefined(const DBus::CallMessage& call,
+ const uint32_t& slot) {
+ return IsDefinedMakeReply(call, nvram_->is_defined(slot));
+}
+
+DBus::Message Tpmd::IsLocked(const DBus::CallMessage& call,
+ const uint32_t& slot) {
+ return IsLockedMakeReply(call, nvram_->is_locked(slot));
+}
+
+DBus::Message Tpmd::Size(const DBus::CallMessage& call, const uint32_t& slot) {
+ return SizeMakeReply(call, nvram_->size(slot));
+}
+
+} // namespace tpmd
diff --git a/src/tpmd.h b/src/tpmd.h
new file mode 100644
index 0000000..eff77f0
--- /dev/null
+++ b/src/tpmd.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TPMD_H
+#define TPMD_H
+
+#include "adaptors/org.chromium.tpmd.h"
+#include "adaptors/org.chromium.tpmd.nvram.h"
+
+namespace tpmd {
+
+class Nvram;
+
+class Tpmd : public org::chromium::tpmd_adaptor,
+ public org::chromium::tpmd::nvram_adaptor,
+ public DBus::ObjectAdaptor,
+ public DBus::IntrospectableAdaptor,
+ public DBus::PropertiesAdaptor {
+ public:
+ Tpmd(DBus::Connection* connection, DBus::BusDispatcher* dispatcher);
+ virtual ~Tpmd();
+
+ virtual bool Init();
+ virtual void Run();
+
+ typedef std::map<std::string, DBus::Variant> VariantMap;
+ typedef std::vector<uint8_t> Bytes;
+
+ // DBus interface: org.chromium.tpmd
+ virtual DBus::Message Encrypt(const DBus::CallMessage& call,
+ const Bytes& plaintext);
+ virtual DBus::Message Decrypt(const DBus::CallMessage& call,
+ const Bytes& ciphertext);
+ virtual DBus::Message GetStatus(const DBus::CallMessage& call);
+
+ // DBus interface: org.chromium.tpmd.nvram
+ virtual DBus::Message Allocate(const DBus::CallMessage& call,
+ const uint32_t& slot, const uint32_t& size,
+ const VariantMap& opts);
+ virtual DBus::Message Free(const DBus::CallMessage& call,
+ const uint32_t& slot);
+ virtual DBus::Message Write(const DBus::CallMessage& call,
+ const uint32_t& slot, const Bytes& bytes);
+ virtual DBus::Message Read(const DBus::CallMessage& call,
+ const uint32_t& slot);
+ virtual DBus::Message IsDefined(const DBus::CallMessage& call,
+ const uint32_t& slot);
+ virtual DBus::Message IsLocked(const DBus::CallMessage& call,
+ const uint32_t& slot);
+ virtual DBus::Message Size(const DBus::CallMessage& call,
+ const uint32_t& slot);
+
+ private:
+ DBus::Connection* dbus_;
+ DBus::BusDispatcher* dispatcher_;
+ Nvram* nvram_;
+};
+
+} // namespace tpmd
+
+#endif // !TPMD_H
diff --git a/src/volatile_nvram.cc b/src/volatile_nvram.cc
new file mode 100644
index 0000000..802e5aa
--- /dev/null
+++ b/src/volatile_nvram.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "volatile_nvram.h"
+
+#include <base/logging.h>
+
+namespace tpmd {
+
+VolatileNvram::VolatileNvram() {
+ for (unsigned int i = 0; i < MAX_SLOTS; i++)
+ slots_[i].bytes_.clear();
+}
+
+VolatileNvram::~VolatileNvram() {}
+
+bool VolatileNvram::Allocate(uint32_t slot, size_t size, uint32_t flags) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS || size == 0 || slots_[slot].bytes_.size() != 0) {
+ return false;
+ }
+ slots_[slot].bytes_.resize(size);
+ memset(&slots_[slot].bytes_[0], 0, size);
+ if (flags & NVRAM_LOCKONCE)
+ slots_[slot].lockonce_ = true;
+ slots_[slot].locked_ = false;
+ slots_[slot].defined_ = false;
+ return true;
+}
+
+bool VolatileNvram::Free(uint32_t slot) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS || slots_[slot].bytes_.size() == 0)
+ return false;
+ slots_[slot].bytes_.clear();
+ return true;
+}
+
+bool VolatileNvram::Write(uint32_t slot, const Bytes& bytes) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS || slots_[slot].bytes_.size() != bytes.size())
+ return false;
+ if (slots_[slot].locked_)
+ return false;
+ memcpy(&slots_[slot].bytes_[0], &bytes[0], bytes.size());
+ if (slots_[slot].lockonce_ && !slots_[slot].defined_)
+ slots_[slot].locked_ = true;
+ slots_[slot].defined_ = true;
+ return true;
+}
+
+bool VolatileNvram::Read(uint32_t slot, Bytes* bytes) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS || slots_[slot].bytes_.size() == 0)
+ return false;
+ if (!slots_[slot].defined_)
+ return false;
+ bytes->resize(slots_[slot].bytes_.size());
+ memcpy(&bytes->at(0), &slots_[slot].bytes_[0], bytes->size());
+ return true;
+}
+
+bool VolatileNvram::is_defined(uint32_t slot) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS)
+ return false;
+ return slots_[slot].defined_;
+}
+
+bool VolatileNvram::is_locked(uint32_t slot) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS)
+ return false;
+ return slots_[slot].locked_;
+}
+
+size_t VolatileNvram::size(uint32_t slot) {
+ slot -= NVRAM_BASE;
+ if (slot >= MAX_SLOTS)
+ return 0;
+ return slots_[slot].bytes_.size();
+}
+
+size_t VolatileNvram::num_slots() {
+ return VolatileNvram::MAX_SLOTS;
+}
+
+} // namespace tpmd
diff --git a/src/volatile_nvram.h b/src/volatile_nvram.h
new file mode 100644
index 0000000..a5c5b96
--- /dev/null
+++ b/src/volatile_nvram.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "nvram.h"
+
+namespace tpmd {
+
+class VolatileNvram : public Nvram {
+ public:
+ VolatileNvram();
+ virtual ~VolatileNvram();
+ virtual bool Allocate(uint32_t slot, size_t len, uint32_t flags);
+ virtual bool Free(uint32_t slot);
+ virtual bool Write(uint32_t slot, const Bytes& bytes);
+ virtual bool Read(uint32_t slot, Bytes* bytes);
+ virtual bool is_defined(uint32_t slot);
+ virtual bool is_locked(uint32_t slot);
+ virtual size_t size(uint32_t slot);
+ virtual size_t num_slots();
+
+ private:
+ static const unsigned int MAX_SLOTS = 16;
+ static const unsigned int NVRAM_BASE = 0x20000000;
+
+ struct BackingSlot {
+ bool lockonce_;
+ bool locked_;
+ bool defined_;
+ Bytes bytes_;
+ };
+
+ struct BackingSlot slots_[MAX_SLOTS];
+};
+
+} // namespace tpmd