Import ceph_14.2.15-4.debian.tar.xz
authorThomas Goirand <zigo@debian.org>
Sun, 13 Dec 2020 15:33:57 +0000 (15:33 +0000)
committerThomas Goirand <zigo@debian.org>
Sun, 13 Dec 2020 15:33:57 +0000 (15:33 +0000)
[dgit import tarball ceph 14.2.15-4 ceph_14.2.15-4.debian.tar.xz]

131 files changed:
.gitlab-ci.yml [new file with mode: 0644]
README.Debian [new file with mode: 0644]
calc-max-parallel.sh [new file with mode: 0755]
ceph-base.ceph.init [new symlink]
ceph-base.dirs [new file with mode: 0644]
ceph-base.docs [new file with mode: 0644]
ceph-base.install [new file with mode: 0644]
ceph-base.postinst [new file with mode: 0644]
ceph-base.postrm [new file with mode: 0644]
ceph-common.dirs [new file with mode: 0644]
ceph-common.install [new file with mode: 0755]
ceph-common.lintian-overrides [new file with mode: 0644]
ceph-common.manpages [new file with mode: 0644]
ceph-common.postinst [new file with mode: 0644]
ceph-common.postrm [new file with mode: 0644]
ceph-common.preinst [new file with mode: 0644]
ceph-common.rbdmap.init [new symlink]
ceph-fs-common.install [new file with mode: 0644]
ceph-fuse.install [new file with mode: 0644]
ceph-fuse.lintian-overrides [new file with mode: 0644]
ceph-fuse.manpages [new file with mode: 0644]
ceph-mds.dirs [new file with mode: 0644]
ceph-mds.install [new file with mode: 0644]
ceph-mds.lintian-overrides [new file with mode: 0644]
ceph-mds.postinst [new file with mode: 0644]
ceph-mgr-dashboard.install [new file with mode: 0644]
ceph-mgr-dashboard.postinst [new file with mode: 0644]
ceph-mgr-diskprediction-cloud.install [new file with mode: 0644]
ceph-mgr-diskprediction-cloud.postinst [new file with mode: 0644]
ceph-mgr-diskprediction-local.install [new file with mode: 0644]
ceph-mgr-diskprediction-local.postinst [new file with mode: 0644]
ceph-mgr-k8sevents.install [new file with mode: 0644]
ceph-mgr-k8sevents.postinst [new file with mode: 0644]
ceph-mgr-rook.install [new file with mode: 0644]
ceph-mgr-rook.postinst [new file with mode: 0644]
ceph-mgr-ssh.install [new file with mode: 0644]
ceph-mgr-ssh.postinst [new file with mode: 0644]
ceph-mgr.dirs [new file with mode: 0644]
ceph-mgr.install [new file with mode: 0644]
ceph-mgr.postinst [new file with mode: 0644]
ceph-mon.dirs [new file with mode: 0644]
ceph-mon.install [new file with mode: 0644]
ceph-mon.postinst [new file with mode: 0644]
ceph-osd.dirs [new file with mode: 0644]
ceph-osd.install [new file with mode: 0644]
ceph-resource-agents.install [new file with mode: 0644]
ceph.NEWS [new file with mode: 0644]
ceph.lintian-overrides [new file with mode: 0644]
cephfs-shell.install [new file with mode: 0644]
changelog [new file with mode: 0644]
clean [new file with mode: 0644]
compat [new file with mode: 0644]
control [new file with mode: 0644]
copyright [new file with mode: 0644]
gbp.conf [new file with mode: 0644]
lib-systemd/system-sleep/ceph [new file with mode: 0755]
lib-systemd/system/ceph-create-keys.service [new file with mode: 0644]
lib-systemd/system/ceph-mds.service [new file with mode: 0644]
lib-systemd/system/ceph-mon.service [new file with mode: 0644]
lib-systemd/system/ceph-osd@.service [new file with mode: 0644]
libcephfs-dev.install [new file with mode: 0644]
libcephfs-java.jlibs [new file with mode: 0644]
libcephfs-jni.install [new file with mode: 0644]
libcephfs-jni.lintian-overrides [new file with mode: 0644]
libcephfs2.install [new file with mode: 0644]
libcephfs2.lintian-overrides [new file with mode: 0644]
libcephfs2.symbols [new file with mode: 0644]
librados-dev.install [new file with mode: 0644]
librados2.install [new file with mode: 0644]
librados2.lintian-overrides [new file with mode: 0644]
librados2.symbols [new file with mode: 0644]
libradospp-dev.install [new file with mode: 0644]
libradosstriper-dev.install [new file with mode: 0644]
libradosstriper1.install [new file with mode: 0644]
libradosstriper1.symbols [new file with mode: 0644]
librbd-dev.install [new file with mode: 0644]
librbd1.install [new file with mode: 0644]
librbd1.symbols [new file with mode: 0644]
librgw-dev.install [new file with mode: 0644]
librgw2.install [new file with mode: 0644]
man/ceph-crush-location.1 [new file with mode: 0644]
man/mount.fuse.ceph.8 [new file with mode: 0644]
missing-sources/bootstrap.js [new file with mode: 0644]
missing-sources/two.js [new file with mode: 0644]
patches/32bit-avoid-overloading.patch [new file with mode: 0644]
patches/32bit-avoid-size_t.patch [new file with mode: 0644]
patches/add-option-to-disable-ceph-dencoder.patch [new file with mode: 0644]
patches/another-cmakelists-fix.patch [new file with mode: 0644]
patches/bluefs-use-uint64_t-for-len.patch [new file with mode: 0644]
patches/civetweb-755-1.8-somaxconn-configurable.patch [new file with mode: 0644]
patches/civetweb-755-1.8-somaxconn-configurable_conf.patch [new file with mode: 0644]
patches/civetweb-755-1.8-somaxconn-configurable_test.patch [new file with mode: 0644]
patches/cmake_add_1.74_to_known_versions.patch [new file with mode: 0644]
patches/cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost.Asio_users.patch [new file with mode: 0644]
patches/debian-armel-armhf-buildflags.patch [new file with mode: 0644]
patches/disable-crypto.patch [new file with mode: 0644]
patches/fix-bash-completion-location [new file with mode: 0644]
patches/make-ceph-python-3.9-aware.patch [new file with mode: 0644]
patches/mds-purgequeue-use_uint64_t.patch [new file with mode: 0644]
patches/riscv64-link-pthread.patch [new file with mode: 0644]
patches/series [new file with mode: 0644]
patches/update-java-source-target-flags.patch [new file with mode: 0644]
python3-ceph-argparse.install [new file with mode: 0644]
python3-ceph.lintian-overrides [new file with mode: 0644]
python3-cephfs.install [new file with mode: 0644]
python3-rados.install [new file with mode: 0644]
python3-rbd.install [new file with mode: 0644]
python3-rgw.install [new file with mode: 0644]
rados-objclass-dev.install [new file with mode: 0644]
radosgw.dirs [new file with mode: 0644]
radosgw.install [new file with mode: 0644]
radosgw.lintian-overrides [new file with mode: 0644]
radosgw.postinst [new file with mode: 0644]
radosgw.prerm [new file with mode: 0644]
rbd-fuse.install [new file with mode: 0644]
rbd-mirror.install [new file with mode: 0644]
rbd-nbd.install [new file with mode: 0644]
rest-bench.install [new file with mode: 0644]
rules [new file with mode: 0755]
source.lintian-overrides [new file with mode: 0644]
source/format [new file with mode: 0644]
source/lintian-overrides [new file with mode: 0644]
source/options [new file with mode: 0644]
tests/build-rados [new file with mode: 0755]
tests/build-rbd [new file with mode: 0755]
tests/ceph-client [new file with mode: 0755]
tests/control [new file with mode: 0644]
tests/python-ceph [new file with mode: 0755]
udev/95-ceph-osd-lvm.rules [new file with mode: 0644]
watch [new file with mode: 0644]
workarounds/ceph-dencoder-oom [new file with mode: 0644]

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644 (file)
index 0000000..9815b7c
--- /dev/null
@@ -0,0 +1,14 @@
+include:
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml
+
+variables:
+ RELEASE: 'unstable'
+ SALSA_CI_DISABLE_APTLY: 0
+ SALSA_CI_DISABLE_AUTOPKGTEST: 0
+ SALSA_CI_DISABLE_BLHC: 0
+ SALSA_CI_DISABLE_LINTIAN: 0
+ SALSA_CI_DISABLE_PIUPARTS: 0
+ SALSA_CI_DISABLE_REPROTEST: 1
+ SALSA_CI_DISABLE_BUILD_PACKAGE_ALL: 1
+ SALSA_CI_DISABLE_BUILD_PACKAGE_ANY: 1
diff --git a/README.Debian b/README.Debian
new file mode 100644 (file)
index 0000000..be21ad7
--- /dev/null
@@ -0,0 +1,120 @@
+## See online installation and setup documentation at
+
+    http://ceph.com/docs/master/install/manual-deployment/
+
+-------- -------- --------
+
+## "systemd" requires manual activation of services:
+
+    ## MON
+    # systemctl start ceph-mon
+    # systemctl enable ceph-mon
+
+    ## OSD.0 (set other OSDs like this)
+    # systemctl start ceph-osd@0
+    # systemctl enable ceph-osd@0
+
+    ## MDS
+    # systemctl start ceph-mds
+    # systemctl enable ceph-mds
+
+    ## "ceph" meta-service (starts/stops all the above like old init script)
+    # systemctl start ceph
+    # systemctl enable ceph
+
+ The ceph cluster can be set in the "/etc/default/ceph" file
+ by setting the CLUSTER environment variable.
+
+-------- -------- --------
+
+## Upgrade procedure (0.72.2 to 0.80):
+
+ * Read "Upgrade Sequencing" in release notes:
+
+    http://ceph.com/docs/firefly/release-notes/
+
+ * Upgrade packages.
+
+ * Restart MONs.
+
+ * Restart all OSDs.
+
+ * Run `ceph osd crush tunables default`.
+
+ * (Restart MDSes).
+
+ * Consider setting the 'hashpspool' flag on your pools (new default):
+
+    ceph osd pool set {pool} hashpspool true
+
+    This changes the pool to use a new hashing algorithm for the distribution of
+    Placement Groups (PGs) to OSDs. This new algorithm ensures a better distribution
+    to all OSDs. Be aware that this change will temporarly put some of your PGs into
+    "misplaced" state and cause additional I/O until all PGs are moved to their new
+    location. See http://tracker.ceph.com/issues/4128 for the details about the new
+    algorithm.
+
+ Read more about tunables in
+
+    http://ceph.com/docs/master/rados/operations/crush-map/#tunables
+
+ Upgrading all OSDs and setting correct tunables is necessary to avoid the errors like:
+
+    ## rbdmap errors:
+    libceph: mon2 192.168.0.222:6789 socket error on read
+
+ Wrong tunables may produce the following error:
+
+    libceph: mon0 192.168.0.222:6789 socket error on read
+    libceph: mon2 192.168.0.250:6789 feature set mismatch, my 4a042a42 < server's 2004a042a42, missing 20000000000
+
+    ## MDS errors:
+    one or more OSDs do not support TMAP2OMAP; upgrade OSDs before starting MDS (or downgrade MDS)
+
+ See also:
+
+    http://ceph.com/docs/firefly/install/upgrading-ceph/
+
+-------- -------- --------
+
+ Jerasure pool(s) will bump requirements to Linux_3.15 (not yet released) for
+ kernel CephFS and RBD clients.
+
+-------- -------- --------
+
+ RBD kernel driver do not support authentication so the following setting
+ in "/etc/ceph/ceph.conf" may be used to relax client auth. requirements:
+
+    cephx cluster require signatures = true
+    cephx service require signatures = false
+
+-------- -------- --------
+
+> How to mount CephFS using fuse client from "/etc/fstab"?
+
+ Add (and modify) the following sample to "/etc/fstab":
+
+    mount.fuse.ceph#conf=/etc/ceph/ceph.conf,id=admin    /mnt/ceph     fuse     _netdev,noatime,allow_other     0    0
+
+ This is equivalent of running
+
+    ceph-fuse /mnt/ceph --id=admin -o noatime,allow_other
+
+ as root.
+
+-------- -------- --------
+
+ To avoid known issue with kernel FS client it is recommended to use
+ 'readdir_max_entries' mount option, for example:
+
+    mount -t ceph 1.2.3.4:/ /mnt/ceph -o readdir_max_entries=64
+
+-------- -------- --------
+
+ Beware of "mlocate" scanning of OSD file systems. To avoid problems add
+ "/var/lib/ceph" to PRUNEPATHS in the "/etc/updatedb.conf" like in the
+ following example:
+
+    PRUNEPATHS="/tmp /var/spool /media /mnt /var/lib/ceph"
+
+-------- -------- --------
diff --git a/calc-max-parallel.sh b/calc-max-parallel.sh
new file mode 100755 (executable)
index 0000000..3593955
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Simple tool to calculate max parallel jobs based on
+# memory of builder.
+#
+# MDCache.cc generally runs out of RAM in 4G of memory
+# with parallel=4
+
+total_ram=$(grep MemTotal /proc/meminfo | awk '{ print $2 }')
+
+sixtyfour_g=$((64*1024*1024))
+fourtyheight_g=$((48*1024*1024))
+thirtytwo_g=$((32*1024*1024))
+sixteen_g=$((16*1024*1024))
+eight_g=$((8*1024*1024))
+four_g=$((4*1024*1024))
+
+if [ ${total_ram} -le ${four_g} ]; then
+    echo "--max-parallel=1"
+elif [ ${total_ram} -le ${eight_g} ]; then
+    echo "--max-parallel=2"
+elif [ ${total_ram} -le ${sixteen_g} ]; then
+    echo "--max-parallel=3"
+elif [ ${total_ram} -le ${thirtytwo_g} ]; then
+    echo "--max-parallel=6"
+elif [ ${total_ram} -le ${fourtyheight_g} ]; then
+    echo "--max-parallel=8"
+elif [ ${total_ram} -le ${sixtyfour_g} ]; then
+    echo "--max-parallel=12"
+else
+    echo "--max-parallel=16"
+fi
diff --git a/ceph-base.ceph.init b/ceph-base.ceph.init
new file mode 120000 (symlink)
index 0000000..b538109
--- /dev/null
@@ -0,0 +1 @@
+../src/init-ceph
\ No newline at end of file
diff --git a/ceph-base.dirs b/ceph-base.dirs
new file mode 100644 (file)
index 0000000..cf605ad
--- /dev/null
@@ -0,0 +1,9 @@
+var/lib/ceph/bootstrap-mds
+var/lib/ceph/bootstrap-mgr
+var/lib/ceph/bootstrap-osd
+var/lib/ceph/bootstrap-rbd
+var/lib/ceph/bootstrap-rbd-mirror
+var/lib/ceph/bootstrap-rgw
+var/lib/ceph/crash
+var/lib/ceph/crash/posted
+var/lib/ceph/tmp
diff --git a/ceph-base.docs b/ceph-base.docs
new file mode 100644 (file)
index 0000000..b43bf86
--- /dev/null
@@ -0,0 +1 @@
+README.md
diff --git a/ceph-base.install b/ceph-base.install
new file mode 100644 (file)
index 0000000..f1d9859
--- /dev/null
@@ -0,0 +1,20 @@
+## install from source tree
+lib/systemd/system/ceph-crash.service
+usr/bin/ceph-crash
+usr/bin/ceph-kvstore-tool
+usr/bin/ceph-run
+usr/bin/crushtool
+usr/bin/monmaptool
+usr/bin/osdmaptool
+usr/lib/*/ceph/erasure-code/*
+usr/lib/*/rados-classes/*
+usr/lib/ceph/ceph_common.sh
+usr/sbin/ceph-create-keys
+usr/share/doc/ceph/sample.ceph.conf
+usr/share/man/man8/ceph-create-keys.8
+usr/share/man/man8/ceph-deploy.8
+usr/share/man/man8/ceph-kvstore-tool.8
+usr/share/man/man8/ceph-run.8
+usr/share/man/man8/crushtool.8
+usr/share/man/man8/monmaptool.8
+usr/share/man/man8/osdmaptool.8
diff --git a/ceph-base.postinst b/ceph-base.postinst
new file mode 100644 (file)
index 0000000..23712a8
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+# The current action is to simply remove the mistakenly-added
+# /etc/init/ceph.conf file; this could be done in any of these cases,
+# although technically it will leave the system in a different state
+# than the original install that included that file.  So instead we
+# only remove on "configure", since that's the only time we know we're
+# successful in installing a newer package than the erroneous version.
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+[ -f "/etc/default/ceph" ] && . /etc/default/ceph
+[ -z "$SERVER_USER" ] && SERVER_USER=ceph
+[ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+
+case "$1" in
+       configure)
+               rm -f /etc/init/ceph.conf
+               for DIR in `ls -1 /var/lib/ceph` ; do
+                   if ! dpkg-statoverride --list /var/lib/ceph/$DIR >/dev/null; then
+                       if [ -d /run/systemd/system ] && [ $DIR = 'mon' ]; then
+                           # NOTE: upgrade file permissions for mon filesystem on
+                           #       systemd based installs only due to automatic
+                           #       restarting of ceph-mon daemon
+                           chown -R $SERVER_USER:$SERVER_GROUP /var/lib/ceph/$DIR
+                       else
+                            chown $SERVER_USER:$SERVER_GROUP /var/lib/ceph/$DIR
+                       fi
+                   fi
+               done
+       ;;
+       abort-upgrade|abort-remove|abort-deconfigure)
+               :
+       ;;
+       *)
+               echo "postinst called with unknown argument \`$1'" >&2
+               exit 1
+       ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/ceph-base.postrm b/ceph-base.postrm
new file mode 100644 (file)
index 0000000..ce3affc
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -e
+
+if [ "${1}" = "purge" ] ; then
+       rm -rf /var/log/ceph 
+       rm -rf /etc/ceph
+fi
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-common.dirs b/ceph-common.dirs
new file mode 100644 (file)
index 0000000..ff05698
--- /dev/null
@@ -0,0 +1,3 @@
+etc/ceph
+var/lib/ceph
+var/log/ceph
diff --git a/ceph-common.install b/ceph-common.install
new file mode 100755 (executable)
index 0000000..9f6d385
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/dh-exec --with=install
+usr/share/bash-completion/completions/ceph
+usr/share/bash-completion/completions/rados
+usr/share/bash-completion/completions/radosgw-admin
+usr/share/bash-completion/completions/rbd
+lib/systemd/system/ceph.target
+lib/systemd/system/rbdmap.service
+etc/default/ceph
+usr/bin/ceph
+usr/bin/ceph-authtool
+usr/bin/ceph-conf
+usr/bin/ceph-dencoder
+usr/bin/ceph-rbdnamer
+usr/bin/ceph-syn
+usr/bin/cephfs-data-scan
+usr/bin/cephfs-journal-tool
+usr/bin/cephfs-table-tool
+usr/bin/rados
+usr/bin/radosgw-admin
+usr/bin/rbd
+usr/bin/rbdmap
+usr/bin/rbd-replay*
+usr/bin/ceph-post-file
+usr/sbin/mount.ceph sbin
+usr/lib/*/ceph/compressor/*
+usr/lib/*/ceph/crypto/* [amd64]
+usr/share/man/man8/ceph-authtool.8
+usr/share/man/man8/ceph-conf.8
+usr/share/man/man8/ceph-dencoder.8
+usr/share/man/man8/ceph-rbdnamer.8
+usr/share/man/man8/ceph-syn.8
+usr/share/man/man8/ceph-post-file.8
+usr/share/man/man8/ceph.8
+usr/share/man/man8/mount.ceph.8
+usr/share/man/man8/rados.8
+usr/share/man/man8/radosgw-admin.8
+usr/share/man/man8/rbd.8
+usr/share/man/man8/rbdmap.8
+usr/share/man/man8/rbd-replay*.8
+usr/share/ceph/known_hosts_drop.ceph.com
+usr/share/ceph/id_rsa_drop.ceph.com
+usr/share/ceph/id_rsa_drop.ceph.com.pub
+etc/ceph/rbdmap
+lib/udev/rules.d/50-rbd.rules
diff --git a/ceph-common.lintian-overrides b/ceph-common.lintian-overrides
new file mode 100644 (file)
index 0000000..d4041ca
--- /dev/null
@@ -0,0 +1,2 @@
+# False-positives:
+spelling-error-in-binary * tEH the
diff --git a/ceph-common.manpages b/ceph-common.manpages
new file mode 100644 (file)
index 0000000..643fa2a
--- /dev/null
@@ -0,0 +1 @@
+debian/man/ceph-crush-location.1
diff --git a/ceph-common.postinst b/ceph-common.postinst
new file mode 100644 (file)
index 0000000..5535e9d
--- /dev/null
@@ -0,0 +1,140 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+# Let the admin override these distro-specified defaults.  This is NOT
+# recommended!
+[ -f "/etc/default/ceph" ] && . /etc/default/ceph
+
+[ -z "$SERVER_HOME" ] && SERVER_HOME=/var/lib/ceph
+[ -z "$SERVER_USER" ] && SERVER_USER=ceph
+[ -z "$SERVER_NAME" ] && SERVER_NAME="Ceph storage service"
+[ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+[ -z "$SERVER_UID" ] && SERVER_UID=64045  # alloc by Debian base-passwd maintainer
+[ -z "$SERVER_GID" ] && SERVER_GID=$SERVER_UID
+
+
+# Groups that the user will be added to, if undefined, then none.
+[ -z "$SERVER_ADDGROUP" ] && SERVER_ADDGROUP=
+
+# Custom dpkg-maintscript-helper type function to deal with
+# nested /etc/default/ceph/ceph
+finish_mv_ceph_defaults() {
+    rm -rf "/etc/default/ceph.dpkg-backup/ceph.dpkg-remove"
+
+    [ -e "/etc/default/ceph.dpkg-backup/ceph" ] || return 0
+
+    echo "Preserving user changes to /etc/default/ceph (renamed from /etc/default/ceph/ceph)..."
+    if [ -f "/etc/default/ceph" ]; then
+        mv -f "/etc/default/ceph" "/etc/default/ceph.dpkg-new"
+    fi
+    mv -f "/etc/default/ceph.dpkg-backup/ceph" "/etc/default/ceph"
+}
+
+case "$1" in
+    configure)
+       # create user to avoid running server as root
+       # 1. create group if not existing
+       if ! getent group | grep -q "^$SERVER_GROUP:" ; then
+          echo -n "Adding group $SERVER_GROUP.."
+          addgroup --quiet --system --gid $SERVER_GID \
+             $SERVER_GROUP 2>/dev/null ||true
+         echo "..done"
+       fi
+       # 2. create user if not existing
+       if ! getent passwd | grep -q "^$SERVER_USER:"; then
+        echo -n "Adding system user $SERVER_USER.."
+         adduser --quiet \
+                 --system \
+                 --no-create-home \
+                 --disabled-password \
+                --uid $SERVER_UID \
+                --gid $SERVER_GID \
+                --home $SERVER_HOME \
+                 $SERVER_USER 2>/dev/null || true
+        echo "..done"
+       fi
+       # 3. adjust passwd entry
+       echo -n "Setting system user $SERVER_USER properties.."
+       usermod -c "$SERVER_NAME" \
+               -d $SERVER_HOME   \
+               -g $SERVER_GROUP  \
+               $SERVER_USER
+
+       # Unlock $SERVER_USER in case it is locked from an uninstall
+       if [ -f /etc/shadow ]; then
+           usermod -U -e '' $SERVER_USER
+       else
+          usermod -U $SERVER_USER
+       fi
+       echo "..done"
+
+       # 5. adjust file and directory permissions
+       if ! dpkg-statoverride --list $SERVER_HOME >/dev/null
+       then
+           chown $SERVER_USER:$SERVER_GROUP $SERVER_HOME
+           chmod u=rwx,g=rx,o= $SERVER_HOME
+       fi
+       if ! dpkg-statoverride --list /var/log/ceph >/dev/null
+       then
+           chown -R $SERVER_USER:$SERVER_GROUP /var/log/ceph
+          # members of group ceph can log here, but cannot remove
+          # others' files.  non-members cannot read any logs.
+           chmod u=rwx,g=rwxs,o=t /var/log/ceph
+       fi
+
+       # 6. fix /var/run/ceph
+       if [ -d /var/run/ceph ]; then
+          echo -n "Fixing /var/run/ceph ownership.."
+          chown $SERVER_USER:$SERVER_GROUP /var/run/ceph
+          echo "..done"
+       fi
+
+       # create /run/ceph.  fail softly if systemd isn't present or
+       # something.
+       [ -x /bin/systemd-tmpfiles ] && systemd-tmpfiles --create || true
+
+       # Complete renames of /etc/default/ceph
+       if [ -n "$2" ] &&
+          dpkg --compare-versions -- "$2" le-nl 10.2.1-0ubuntu1; then
+           finish_mv_ceph_defaults
+          # Preserve dpkg-backup directory if it still contains
+          # any file
+           if ! ls -1qA "/etc/default/ceph.dpkg-backup" | grep -q . ; then
+              rm -rf "/etc/default/ceph.dpkg-backup"
+           fi
+       fi
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/ceph-common.postrm b/ceph-common.postrm
new file mode 100644 (file)
index 0000000..e405118
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+# postrm script for ceph-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postrm> `remove'
+#        * <postrm> `purge'
+#        * <old-postrm> `upgrade' <new-version>
+#        * <new-postrm> `failed-upgrade' <old-version>
+#        * <new-postrm> `abort-install'
+#        * <new-postrm> `abort-install' <old-version>
+#        * <new-postrm> `abort-upgrade' <old-version>
+#        * <disappearer's-postrm> `disappear' <overwriter>
+#          <overwriter-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+# Custom dpkg-maintscript-helper type function to deal with
+# nested /etc/default/ceph/ceph
+abort_mv_ceph_defaults() {
+    if [ -e "/etc/default/ceph.dpkg-backup/ceph.dpkg-remove" ]; then
+        echo "Reinstalling /etc/default/ceph/ceph that was moved away"
+        mv "/etc/default/ceph.dpkg-backup" "/etc/default/ceph"
+        mv "/etc/default/ceph/ceph.dpkg-remove" "/etc/default/ceph/ceph"
+    fi
+}
+
+case "$1" in
+    remove)
+    ;;
+
+    purge)
+        [ -f "/etc/default/ceph" ] && . /etc/default/ceph
+        [ -z "$SERVER_USER" ] && SERVER_USER=ceph
+
+       rm -rf /var/log/ceph
+       rm -rf /etc/ceph
+
+        if [ -f /etc/shadow ]; then
+            usermod -L -e 1 $SERVER_USER
+        else
+            usermod -L $SERVER_USER
+        fi
+
+    ;;
+
+    abort-install|abort-upgrade)
+        if [ -n "$2" ] &&
+           dpkg --compare-versions -- "$2" le-nl 10.2.1-0ubuntu1; then
+            abort_mv_ceph_defaults
+        fi
+    ;;
+
+    upgrade|failed-upgrade|disappear)
+    ;;
+
+    *)
+        echo "postrm called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-common.preinst b/ceph-common.preinst
new file mode 100644 (file)
index 0000000..ef14f1e
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+# Custom dpkg-maintscript-helper type function to deal with
+# nested /etc/default/ceph/ceph
+prepare_mv_ceph_defaults() {
+    local md5sum old_md5sum
+    md5sum="$(md5sum "/etc/default/ceph/ceph" | sed -e 's/ .*//')"
+    old_md5sum="$(dpkg-query -W -f='${Conffiles}' "ceph-common" | \
+        sed -n -e "\'^ /etc/default/ceph/ceph ' { s/ obsolete$//; s/.* //; p }")"
+    if [ "$md5sum" = "$old_md5sum" ]; then
+        mv -f "/etc/default/ceph/ceph" "/etc/default/ceph/ceph.dpkg-remove"
+        mv -f "/etc/default/ceph" "/etc/default/ceph.dpkg-backup"
+    fi
+}
+
+case "$1" in
+    upgrade|install)
+        if [ -d /etc/default/ceph ] && [ -n "$2" ] &&
+           dpkg --compare-versions -- "$2" le-nl 10.2.1-0ubuntu1; then
+            prepare_mv_ceph_defaults
+        fi
+    ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/ceph-common.rbdmap.init b/ceph-common.rbdmap.init
new file mode 120000 (symlink)
index 0000000..b2de0ce
--- /dev/null
@@ -0,0 +1 @@
+../src/init-rbdmap
\ No newline at end of file
diff --git a/ceph-fs-common.install b/ceph-fs-common.install
new file mode 100644 (file)
index 0000000..a4f0bab
--- /dev/null
@@ -0,0 +1,4 @@
+usr/bin/cephfs
+usr/sbin/mount.ceph sbin
+usr/share/man/man8/cephfs.8
+usr/share/man/man8/mount.ceph.8
diff --git a/ceph-fuse.install b/ceph-fuse.install
new file mode 100644 (file)
index 0000000..e132b79
--- /dev/null
@@ -0,0 +1,4 @@
+lib/systemd/system/ceph-fuse*
+usr/bin/ceph-fuse
+usr/sbin/mount.fuse.ceph sbin
+usr/share/man/man8/ceph-fuse.8
diff --git a/ceph-fuse.lintian-overrides b/ceph-fuse.lintian-overrides
new file mode 100644 (file)
index 0000000..d4041ca
--- /dev/null
@@ -0,0 +1,2 @@
+# False-positives:
+spelling-error-in-binary * tEH the
diff --git a/ceph-fuse.manpages b/ceph-fuse.manpages
new file mode 100644 (file)
index 0000000..e4c9c23
--- /dev/null
@@ -0,0 +1 @@
+debian/man/mount.fuse.ceph.8
diff --git a/ceph-mds.dirs b/ceph-mds.dirs
new file mode 100644 (file)
index 0000000..9845268
--- /dev/null
@@ -0,0 +1 @@
+var/lib/ceph/mds
diff --git a/ceph-mds.install b/ceph-mds.install
new file mode 100644 (file)
index 0000000..a26d559
--- /dev/null
@@ -0,0 +1,3 @@
+lib/systemd/system/ceph-mds*
+usr/bin/ceph-mds
+usr/share/man/man8/ceph-mds.8
diff --git a/ceph-mds.lintian-overrides b/ceph-mds.lintian-overrides
new file mode 100644 (file)
index 0000000..c883954
--- /dev/null
@@ -0,0 +1,13 @@
+# False-positives:
+spelling-error-in-binary * tEH the
+
+# Ceph upstart configuration don't have equivalent init scripts
+ceph-mds: init.d-script-not-marked-as-conffile etc/init.d/ceph-mds-all
+ceph-mds: init.d-script-not-included-in-package etc/init.d/ceph-mds-all
+ceph-mds: init.d-script-not-marked-as-conffile etc/init.d/ceph-mds
+ceph-mds: init.d-script-not-included-in-package etc/init.d/ceph-mds
+ceph-mds: init.d-script-not-marked-as-conffile etc/init.d/ceph-mds-all-starter
+ceph-mds: init.d-script-not-included-in-package etc/init.d/ceph-mds-all-starter
+ceph-mds: postrm-does-not-call-updaterc.d-for-init.d-script etc/init.d/ceph-mds-all
+ceph-mds: postrm-does-not-call-updaterc.d-for-init.d-script etc/init.d/ceph-mds
+ceph-mds: postrm-does-not-call-updaterc.d-for-init.d-script etc/init.d/ceph-mds-all-starter
diff --git a/ceph-mds.postinst b/ceph-mds.postinst
new file mode 100644 (file)
index 0000000..3ae908a
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mds
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+[ -f "/etc/default/ceph" ] && . /etc/default/ceph
+[ -z "$SERVER_USER" ] && SERVER_USER=ceph
+[ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+
+case "$1" in
+       configure)
+                if ! dpkg-statoverride --list /var/lib/ceph/mds >/dev/null;
+               then
+                       chown $SERVER_USER:$SERVER_GROUP /var/lib/ceph/mds
+               fi
+       ;;
+       abort-upgrade|abort-remove|abort-deconfigure)
+               :
+       ;;
+
+       *)
+               echo "postinst called with unknown argument \`$1'" >&2
+               exit 1
+       ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/ceph-mgr-dashboard.install b/ceph-mgr-dashboard.install
new file mode 100644 (file)
index 0000000..8d3c8bd
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/dashboard
diff --git a/ceph-mgr-dashboard.postinst b/ceph-mgr-dashboard.postinst
new file mode 100644 (file)
index 0000000..e681ef6
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-dashboard
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr-diskprediction-cloud.install b/ceph-mgr-diskprediction-cloud.install
new file mode 100644 (file)
index 0000000..58481b2
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/diskprediction_cloud
diff --git a/ceph-mgr-diskprediction-cloud.postinst b/ceph-mgr-diskprediction-cloud.postinst
new file mode 100644 (file)
index 0000000..d8e7a50
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-diskprediction-cloud
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr-diskprediction-local.install b/ceph-mgr-diskprediction-local.install
new file mode 100644 (file)
index 0000000..a381e25
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/diskprediction_local
diff --git a/ceph-mgr-diskprediction-local.postinst b/ceph-mgr-diskprediction-local.postinst
new file mode 100644 (file)
index 0000000..a3293a8
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-diskprediction-local
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr-k8sevents.install b/ceph-mgr-k8sevents.install
new file mode 100644 (file)
index 0000000..734da94
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/k8sevents
diff --git a/ceph-mgr-k8sevents.postinst b/ceph-mgr-k8sevents.postinst
new file mode 100644 (file)
index 0000000..4ab578f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-k8sevents
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr-rook.install b/ceph-mgr-rook.install
new file mode 100644 (file)
index 0000000..50cadb4
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/rook
diff --git a/ceph-mgr-rook.postinst b/ceph-mgr-rook.postinst
new file mode 100644 (file)
index 0000000..a3293a8
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-diskprediction-local
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr-ssh.install b/ceph-mgr-ssh.install
new file mode 100644 (file)
index 0000000..4023e4e
--- /dev/null
@@ -0,0 +1 @@
+usr/share/ceph/mgr/ssh
diff --git a/ceph-mgr-ssh.postinst b/ceph-mgr-ssh.postinst
new file mode 100644 (file)
index 0000000..c0e3b13
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr-ssh
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+    configure)
+       # attempt to load the plugin if the mgr is running
+       deb-systemd-invoke try-restart ceph-mgr.target
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mgr.dirs b/ceph-mgr.dirs
new file mode 100644 (file)
index 0000000..636b3cf
--- /dev/null
@@ -0,0 +1 @@
+var/lib/ceph/mgr
diff --git a/ceph-mgr.install b/ceph-mgr.install
new file mode 100644 (file)
index 0000000..2d82c0d
--- /dev/null
@@ -0,0 +1,28 @@
+lib/systemd/system/ceph-mgr*
+usr/bin/ceph-mgr
+usr/share/ceph/mgr/ansible
+usr/share/ceph/mgr/balancer
+usr/share/ceph/mgr/crash
+usr/share/ceph/mgr/deepsea
+usr/share/ceph/mgr/devicehealth
+usr/share/ceph/mgr/influx
+usr/share/ceph/mgr/insights
+usr/share/ceph/mgr/iostat
+usr/share/ceph/mgr/localpool
+usr/share/ceph/mgr/mgr_module.*
+usr/share/ceph/mgr/mgr_util.*
+usr/share/ceph/mgr/orchestrator.*
+usr/share/ceph/mgr/orchestrator_cli
+usr/share/ceph/mgr/osd_perf_query
+usr/share/ceph/mgr/pg_autoscaler
+usr/share/ceph/mgr/progress
+usr/share/ceph/mgr/prometheus
+usr/share/ceph/mgr/rbd_support
+usr/share/ceph/mgr/restful
+usr/share/ceph/mgr/selftest
+usr/share/ceph/mgr/status
+usr/share/ceph/mgr/telegraf
+usr/share/ceph/mgr/telemetry
+usr/share/ceph/mgr/test_orchestrator
+usr/share/ceph/mgr/volumes
+usr/share/ceph/mgr/zabbix
diff --git a/ceph-mgr.postinst b/ceph-mgr.postinst
new file mode 100644 (file)
index 0000000..6d38ccf
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+# vim: set noet ts=8:
+# postinst script for ceph-mgr
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+[ -f "/etc/default/ceph" ] && . /etc/default/ceph
+[ -z "$SERVER_USER" ] && SERVER_USER=ceph
+[ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+
+case "$1" in
+    configure)
+       [ -x /sbin/start ] && start ceph-mgr-all || :
+
+       if ! dpkg-statoverride --list /var/lib/ceph/mgr >/dev/null
+       then
+            chown $SERVER_USER:$SERVER_GROUP /var/lib/ceph/mgr
+       fi
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-mon.dirs b/ceph-mon.dirs
new file mode 100644 (file)
index 0000000..e2845f6
--- /dev/null
@@ -0,0 +1 @@
+var/lib/ceph/mon
diff --git a/ceph-mon.install b/ceph-mon.install
new file mode 100644 (file)
index 0000000..ff429f1
--- /dev/null
@@ -0,0 +1,4 @@
+lib/systemd/system/ceph-mon*
+usr/bin/ceph-mon
+usr/bin/ceph-monstore-tool
+usr/share/man/man8/ceph-mon.8
diff --git a/ceph-mon.postinst b/ceph-mon.postinst
new file mode 100644 (file)
index 0000000..454ffc1
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/bash
+# vim: set noet ts=8:
+# postinst script for ceph-mon
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#
+#      postinst configure <most-recently-configured-version>
+#      old-postinst abort-upgrade <new-version>
+#      conflictor's-postinst abort-remove in-favour <package> <new-version>
+#      postinst abort-remove
+#      deconfigured's-postinst abort-deconfigure in-favour <failed-install-package> <version> [<removing conflicting-package> <version>]
+#
+
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+[ -f "/etc/default/ceph" ] && . /etc/default/ceph
+[ -z "$SERVER_USER" ] && SERVER_USER=ceph
+[ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+
+case "$1" in
+    configure)
+       :
+    ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       :
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/ceph-osd.dirs b/ceph-osd.dirs
new file mode 100644 (file)
index 0000000..b7fc476
--- /dev/null
@@ -0,0 +1 @@
+var/lib/ceph/osd
diff --git a/ceph-osd.install b/ceph-osd.install
new file mode 100644 (file)
index 0000000..5a23c19
--- /dev/null
@@ -0,0 +1,20 @@
+debian/udev/* lib/udev/rules.d
+etc/sudoers.d/ceph-osd-smartctl
+etc/sysctl.d/30-ceph-osd.conf
+lib/systemd/system/ceph-osd*
+lib/systemd/system/ceph-volume@.service
+usr/bin/ceph-bluestore-tool
+usr/bin/ceph-clsinfo
+usr/bin/ceph-objectstore-tool
+usr/bin/ceph-osd
+usr/bin/ceph-osdomap-tool
+usr/lib/ceph/ceph-osd-prestart.sh
+usr/lib/python*/dist-packages/ceph_volume-*
+usr/lib/python*/dist-packages/ceph_volume/*
+usr/sbin/ceph-volume
+usr/sbin/ceph-volume-systemd
+usr/share/man/man8/ceph-bluestore-tool.8
+usr/share/man/man8/ceph-clsinfo.8
+usr/share/man/man8/ceph-osd.8
+usr/share/man/man8/ceph-volume-systemd.8
+usr/share/man/man8/ceph-volume.8
diff --git a/ceph-resource-agents.install b/ceph-resource-agents.install
new file mode 100644 (file)
index 0000000..30843f6
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/ocf/resource.d/ceph/*
diff --git a/ceph.NEWS b/ceph.NEWS
new file mode 100644 (file)
index 0000000..ee9db2f
--- /dev/null
+++ b/ceph.NEWS
@@ -0,0 +1,180 @@
+ceph (10.2.5-1) unstable; urgency=medium
+
+  ## Upgrades from Debian Jessie
+
+  Online upgrades from Ceph versions prior to Hammer (0.94.x) are not
+  supported by upstream. As Debian Jessie has Ceph Firefly (0.80.x) an
+  online upgrade from Jessie to Stretch is not possible. You have to first
+  shutdown all Ceph daemons on all nodes, upgrade everything to the new
+  version and start all daemons again.
+
+  Ceph daemons are not automatically restarted on upgrade to minimize
+  disruption. You have to manually restart them after the upgrade.
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sun, 08 Jan 2017 14:57:35 +0100
+
+ceph (9.2.0-1) experimental; urgency=medium
+
+  ## systemd Enablement
+
+  For all distributions that support systemd (Debian Jessie 8.x,
+  Ubuntu >= 16.04), Ceph daemons are now managed using upstream provided
+  systemd files instead of the legacy sysvinit scripts or distro provided
+  systemd files.  For example:
+
+    systemctl start ceph.target       # start all daemons
+    systemctl status ceph-osd@12      # check status of osd.12
+
+  To upgrade existing deployments that use the older systemd service
+  configurations (Ubuntu >= 15.04, Debian >= Jessie), you need to switch
+  to using the new ceph-mon@ service:
+
+    systemctl stop ceph-mon
+    systemctl disable ceph-mon
+
+    systemctl start ceph-mon@`hostname`
+    systemctl enable ceph-mon@`hostname`
+
+  and also enable the ceph target post upgrade:
+
+    systemctl enable ceph.target
+
+  The main notable distro that is *not* using systemd is Ubuntu 14.04
+  (The next Ubuntu LTS, 16.04, will use systemd instead of upstart).
+
+  ## Ceph daemons no longer run as root
+
+  Ceph daemons now run as user and group 'ceph' by default.  The
+  ceph user has a static UID assigned by Debian to ensure consistency
+  across servers within a Ceph deployment.
+
+  If your systems already have a ceph user, upgrading the package will cause
+  problems.  We suggest you first remove or rename the existing 'ceph' user
+  and 'ceph' group before upgrading.
+
+  When upgrading, administrators have two options:
+
+  1. Add the following line to 'ceph.conf' on all hosts:
+
+       setuser match path = /var/lib/ceph/$type/$cluster-$id
+
+     This will make the Ceph daemons run as root (i.e., not drop
+     privileges and switch to user ceph) if the daemon's data
+     directory is still owned by root.  Newly deployed daemons will
+     be created with data owned by user ceph and will run with
+     reduced privileges, but upgraded daemons will continue to run as
+     root.
+
+  2. Fix the data ownership during the upgrade.  This is the
+     preferred option, but it is more work and can be very time
+     consuming.  The process for each host is to:
+
+     1. Upgrade the ceph package.  This creates the ceph user and group.  For
+        example:
+
+          apt-get install ceph
+
+        NOTE: the permissions on /var/lib/ceph/mon will be set to ceph:ceph
+              as part of the package upgrade process on existing *systemd*
+              based installations; the ceph-mon systemd service will be
+              automatically restarted as part of the upgrade.  All other
+              filesystem permissions on systemd based installs will
+              remain unmodified by the upgrade.
+
+     2. Stop the daemon(s):
+
+          systemctl stop ceph-osd@*   # debian, ubuntu >= 15.04
+          stop ceph-all               # ubuntu 14.04
+
+     3. Fix the ownership:
+
+          chown -R ceph:ceph /var/lib/ceph
+
+     4. Restart the daemon(s):
+
+          start ceph-all                # ubuntu 14.04
+          systemctl start ceph.target   # debian, ubuntu >= 15.04
+
+     Alternatively, the same process can be done with a single daemon
+     type, for example by stopping only monitors and chowning only
+     '/var/lib/ceph/osd'.
+
+  ## KeyValueStore OSD on-disk format changes
+
+  The on-disk format for the experimental KeyValueStore OSD backend has
+  changed.  You will need to remove any OSDs using that backend before you
+  upgrade any test clusters that use it.
+
+  ## Deprecated commands
+
+  'ceph scrub', 'ceph compact' and 'ceph sync force' are now DEPRECATED.
+  Users should instead use 'ceph mon scrub', 'ceph mon compact' and
+  'ceph mon sync force'.
+
+  ## Full pool behaviour
+
+  When a pool quota is reached, librados operations now block indefinitely,
+  the same way they do when the cluster fills up.  (Previously they would
+  return -ENOSPC).  By default, a full cluster or pool will now block.  If
+  your librados application can handle ENOSPC or EDQUOT errors gracefully,
+  you can get error returns instead by using the new librados
+  OPERATION_FULL_TRY flag.
+
+ -- James Page <james.page@ubuntu.com>  Mon, 30 Nov 2015 09:23:09 +0000
+
+ceph (0.80.9-2) unstable; urgency=medium
+
+  ## CRUSH fixes in 0.80.9
+
+  The 0.80.9 point release fixes several issues with CRUSH that trigger excessive
+  data migration when adjusting OSD weights. These are most obvious when a very
+  small weight change (e.g., a change from 0 to .01) triggers a large amount of
+  movement, but the same set of bugs can also lead to excessive (though less
+  noticeable) movement in other cases.
+
+  However, because the bug may already have affected your cluster, fixing it
+  may trigger movement back to the more correct location. For this reason, you
+  must manually opt-in to the fixed behavior.
+
+  In order to set the new tunable to correct the behavior:
+
+      ceph osd crush set-tunable straw_calc_version 1
+
+  Note that this change will have no immediate effect. However, from this
+  point forward, any ‘straw’ bucket in your CRUSH map that is adjusted will get
+  non-buggy internal weights, and that transition may trigger some rebalancing.
+
+  You can estimate how much rebalancing will eventually be necessary on your
+  cluster with:
+
+      ceph osd getcrushmap -o /tmp/cm
+      crushtool -i /tmp/cm --num-rep 3 --test --show-mappings > /tmp/a 2>&1
+      crushtool -i /tmp/cm --set-straw-calc-version 1 -o /tmp/cm2
+      crushtool -i /tmp/cm2 --reweight -o /tmp/cm2
+      crushtool -i /tmp/cm2 --num-rep 3 --test --show-mappings > /tmp/b 2>&1
+      wc -l /tmp/a                          # num total mappings
+      diff -u /tmp/a /tmp/b | grep -c ^+    # num changed mappings
+
+  Divide the total number of lines in /tmp/a with the number of lines
+  changed.  We've found that most clusters are under 10%.
+
+  You can force all of this rebalancing to happen at once with:
+
+      ceph osd crush reweight-all
+
+  Otherwise, it will happen at some unknown point in the future when
+  CRUSH weights are next adjusted.
+
+  ## Mapping rbd devices with rbdmap on systemd systems
+
+  If you have setup rbd mappings in /etc/ceph/rbdmap and corresponding mounts
+  in /etc/fstab things might break with systemd because systemd waits for the
+  rbd device to appear before the legacy rbdmap init file has a chance to run
+  and drops into emergency mode if it times out.
+
+  This can be fixed by adding the nofail option in /etc/fstab to all rbd
+  backed mount points. With this systemd does not wait for the device and
+  proceeds with the boot process. After rbdmap mapped the device, systemd
+  detects the new device and mounts the file system.
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Mon, 04 May 2015 22:49:48 +0200
diff --git a/ceph.lintian-overrides b/ceph.lintian-overrides
new file mode 100644 (file)
index 0000000..99b3db8
--- /dev/null
@@ -0,0 +1 @@
+empty-binary-package
diff --git a/cephfs-shell.install b/cephfs-shell.install
new file mode 100644 (file)
index 0000000..4713a81
--- /dev/null
@@ -0,0 +1,2 @@
+usr/bin/cephfs-shell
+usr/lib/python3*/dist-packages/cephfs_shell-*.egg-info
diff --git a/changelog b/changelog
new file mode 100644 (file)
index 0000000..70c73d4
--- /dev/null
+++ b/changelog
@@ -0,0 +1,1449 @@
+ceph (14.2.15-4) unstable; urgency=medium
+
+  * Add upstream 3 patches for libboost 1.74 (Closes: #977243).
+
+ -- Thomas Goirand <zigo@debian.org>  Sun, 13 Dec 2020 16:33:57 +0100
+
+ceph (14.2.15-3) unstable; urgency=medium
+
+  [ Adrian Bunk ]
+  * [197afaf] Merge branch 'debian/unstable' into 'debian/unstable'
+    Portability fixes
+    See merge request ceph-team/ceph!6
+
+ -- Thomas Goirand <zigo@debian.org>  Thu, 03 Dec 2020 21:03:06 +0100
+
+ceph (14.2.15-2) unstable; urgency=medium
+
+  * Do not build with clang, instead, set --max-parallel=1, as it seems it
+    worked for Ubuntu. If this doesn't work, we'll disable the non-64 bits
+    arch completely.
+
+ -- Thomas Goirand <zigo@debian.org>  Mon, 30 Nov 2020 21:10:12 +0100
+
+ceph (14.2.15-1) unstable; urgency=medium
+
+  * New upstream release (Closes: #956750):
+    - Fix FTBFS with GCC-10 (Closes: #957079).
+    - Fix CVE-2020-10753 (Closes: #963760).
+    - Fix CVE-2020-25660 (Closes: #975275).
+  * Refreshed patches:
+    - 32bit-avoid-size_t.patch
+    - add-option-to-disable-ceph-dencoder.patch
+    - bluefs-use-uint64_t-for-len.patch
+    - disable-crypto.patch
+    - mds-purgequeue-use_uint64_t.patch
+  * Raw wrap-and-sort -bastk.
+  * Added myself as uploader.
+  * Added librdkafka-dev as build-depends.
+  * Fixed debian/libcephfs-dev.install.
+  * debian/calc-max-parallel.sh: allow for more values of --max-parallel so
+    that ceph builds faster on more powerful machines.
+  * Add a patch to make Ceph aware of Python 3.9:
+    - make-ceph-python-3.9-aware.patch
+  * Add a debian/source/options to ignore CRLF to CR changes.
+  * Use --home in ceph-common.postinst when creating the Ceph system user.
+  * Updated debian/libcephfs2.symbols (added 3 new symbols).
+  * Package: ceph-resource-agents, switch Priority: to optional.
+  * Add debian/source.lintian-overrides to allow .js which shipped by upstream
+    in both compiled and source version.
+  * Removed now useless dpkg-maintscript-helper rm_conffile: they have been
+    around for more than one release.
+  * debian/ceph-osd.postinst: remove as it's doing nothing.
+  * Fix debian/lib{rbd1,rados2}.symbols (3 missing symbols).
+
+ -- Thomas Goirand <zigo@debian.org>  Fri, 27 Nov 2020 23:58:00 +0100
+
+ceph (14.2.9-1) unstable; urgency=high
+
+  * [dc4e7cf] Update upstream source from tag 'upstream/14.2.9'
+    Update to upstream version '14.2.9'
+    with Debian dir 544321a5823a0e5b826198888c79cb3ed4dd9b2e
+    Closing #956142 / CVE-2020-1760
+
+ -- Bernd Zeimetz <bzed@debian.org>  Fri, 24 Apr 2020 22:43:18 +0200
+
+ceph (14.2.8-2) unstable; urgency=medium
+
+  * [eed9184] Fix 32bit issues in src/mds/PurgeQueue.cc
+    mips64el (as reported in the bug report) built fine.
+    s390x is a buildd issue, gets stuck sometimes for unknown (and not
+    reproducible) reasons.
+    Other build issues are fixed hopefully.
+    Thanks to Ivo De Decker (Closes: #953749)
+
+ -- Bernd Zeimetz <bzed@debian.org>  Mon, 23 Mar 2020 00:14:25 +0100
+
+ceph (14.2.8-1) unstable; urgency=medium
+
+  * [e14a030] Update upstream source from tag 'upstream/14.2.8'
+    Update to upstream version '14.2.8'
+    with Debian dir 6c28e7789e84694b28409f0aceb9bfe6f2acdade
+    Closes: #953364
+  * [6bc660e] Refreshing patches
+  * [9e935e3] Fix FTBFS on riscv64.
+    Thanks to Aurelien Jarno (Closes: #953003)
+  * [4287e84] fix lintian override
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 08 Mar 2020 22:31:55 +0100
+
+ceph (14.2.7-1) unstable; urgency=medium
+
+  * [a68d12f] Update upstream source from tag 'upstream/14.2.7'
+    Update to upstream version '14.2.7'
+    with Debian dir 1125b03b88e8da85cf70f7cc540c1c30fa95d456
+    This also contains a fix for
+    [CVE-2020-1700]: Fixed a flaw in RGW beast frontend that
+    could lead to denial of service from an unauthenticated client.
+    CVE-2020-1699 was patched since 14.2.6-3.
+  * [1474595] Removing patches applied upstream
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sat, 01 Feb 2020 00:47:27 +0100
+
+ceph (14.2.6-6) unstable; urgency=medium
+
+  * [c18d632] Remove broken patch (Option::TYPE_SIZE as uint64_t).
+    This patch actually results in a segfault while parsing options.
+    Revert it and then see what it was actually needed for on 32bit.
+    Thanks to Martin Mlynář (Closes: #949743)
+  * [b5c7be8] Update patch with a non-segfaulting version.
+    Took me some time to figure out that in option.cc size_t is not what you
+    expect, but a struct instead, just with the same name.
+    So to make clang happy we'll use static_cast<std::size_t> now, although
+    this will for sure show various other issues on 32bit as not all
+    possible config values will fit into 32bit numbers.
+    Fixing this will need a lot of upstream work unfortunately.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 26 Jan 2020 15:39:29 +0100
+
+ceph (14.2.6-5) unstable; urgency=medium
+
+  * [966df1a] Removing cython from Build-deps.
+    Thanks to Sandro Tosi (Closes: #936282)
+  * [38fdd89] clang ist not available on sh4
+  * [3c97474] Replace findstring by filter where needed.
+    Thanks jrtc27 for the hint.
+  * [c694d0d] Pass -DHAVE_NEON=0 to cmake on armel.
+    Instead of "fixing" CMakeCache.txt.
+  * [825a942] Revert "Don't build ceph on mipsel."
+    This reverts commit 424ea9b82f956daa8fa9c0539d0752ccfdc7caf6.
+    Thanks to peter green (Closes: #949528)
+  * [79aef26] Remove merge fail
+
+ -- Bernd Zeimetz <bzed@debian.org>  Tue, 21 Jan 2020 21:21:17 +0100
+
+ceph (14.2.6-4) unstable; urgency=high
+
+  * Really uploading to unstable now.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sat, 18 Jan 2020 19:58:32 +0100
+
+ceph (14.2.6-3) experimental; urgency=high
+
+  * Uploading to unstable, including changes to make ceph
+    build on mipsel again (Closes: #948722).
+  * [1bac6f0] mgr/dashboard: fix improper URL checking.
+    This change disables up-level references beyond the HTTP base directory.
+    [CVE-2020-1699]
+    Upstream commit 0443e40c11280ba3b7efcba61522afa70c4f8158
+    Thanks to Salvatore Bonaccorso (Closes: #949206)
+  * [720ce76] Updating changelog (from experimental)
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sat, 18 Jan 2020 19:11:22 +0100
+
+ceph (14.2.6-2) experimental; urgency=medium
+
+  * [fc4df2f] d/rules: make sure mips doesn't match on mipsel/mips64el
+  * [3d9ed86] Add patch to disable dencoder build if needed.
+  * [ce0c9bb] Don't build ceph-dencoder on mipsel.
+    Its a debugging tool and does not build on mipsel due to g++/clang++
+    running out of memory.
+    Replacing it by a shellscript that prints an error message.
+  * [0d83e22] Configure gbp for experimental
+
+ -- Bernd Zeimetz <bzed@debian.org>  Thu, 16 Jan 2020 23:59:37 +0100
+
+ceph (14.2.6-1) unstable; urgency=medium
+
+  [ James Page ]
+  * [2e50d5b] Fix misnamed package Recommends brbd1 -> librbd1
+  * [11df8ed] Add missing debhelper misc:Depends for python3-ceph
+  * [08c3c8b] Add missing Depends on python3-{distutils,routes} to ceph-mgr-dashboard package
+
+  [ Bernd Zeimetz ]
+  * [a87f434] Update upstream source from tag 'upstream/14.2.6'
+    Update to upstream version '14.2.6'
+    with Debian dir f37aa9f99ec09cc88d8e5e468c1f642fa7f77ef1
+  * [e91626b] Revert "Fix ceph-mgr - indefinite queue growth hangs"
+    This reverts commit 010db9a30458a6417ff667c3c11a3870edb8ee0c.
+    Patches were applied upstream for 14.2.6
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 12 Jan 2020 23:13:27 +0100
+
+ceph (14.2.5-3) unstable; urgency=medium
+
+  * Uploading to unstable
+
+  * [010db9a] Fix ceph-mgr - indefinite queue growth hangs.
+    Applying the backport for the fix
+    https://github.com/ceph/ceph/pull/32466
+    Thanks to Milan Kupcevic (Closes: #947969)
+  * [b01de37] Merge branch 'debian/unstable' into debian/experimental
+  * [c8f35e5] Add breaks/replaces for ceph-common - ceph mds.
+  * [ee905cb] Revert "Configure gbp for experimental"
+    This reverts commit 3bcd5ac5f416b902a868036c243d7f19752c82f8.
+  * [6303513] Revert "CI: build in experimental"
+    This reverts commit d481122833e611c69c28e2b381e1cc1c8f689385.
+  * [f1a9482] Snapshot changelog
+  * [6e955c8] Removing automatic Ubuntu header
+  * [b90d95a] Mark patch as forwarded
+
+ -- Bernd Zeimetz <bzed@debian.org>  Tue, 07 Jan 2020 20:50:28 +0100
+
+ceph (14.2.5-2) experimental; urgency=medium
+  
+  * [8c74414] lower --max-parallel for >=16GB
+    g++ loves to eat ram
+  * [b15dcdd] Build-dep. on python3-dev instead python3-all-dev.
+    Thanks to Graham Inggs (Closes: #948021)
+  * [d481122] CI: build in experimental
+  * [4303a75] 32bit: fix more size_t vs uint64_t issues.
+  * [c98ea07] Install bash-completion in /usr again.
+    This change went missing somewhere during the import of the
+    changes done in Ubuntu between 12.2.11 and 14.2.4.
+    Thanks to Andreas Beckmann (Closes: #948165)
+  * [c7d90b9] Move manpages to ceph-common again.
+    This also went missing during the import.
+  * [3e5a680] Use a better way to check if we are on 32bit.
+  * [c03cd06] rm d/p/boost-py37-compat.patch.
+    Upstream renamed assert.h to ceph_assert.h, so this patch should not be
+    necessary anymore.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 05 Jan 2020 00:04:23 +0100
+
+ceph (14.2.5-1) experimental; urgency=medium
+
+  [ Bernd Zeimetz ]
+  * [3bcd5ac] Configure gbp for experimental
+  * [bd0b051] New upstream version 14.2.5
+  * [46cbe61] Merge upstream changes for 14.2.5
+  * [4dfd819] Refreshing patches
+  * [da26f25] Fix copy&paste errors in build-deps.
+  * [7ff43a2] Mark build-deps needed for make check.
+    And remove the need to install them.
+  * [5ef8ac3] Remove left over patch file
+  * [91ab5b9] */lib_tp.so files are not built, don't install them.
+  * [44591e4] Don't try to install files we don't build
+  * [db0994e] librbd1.symbols: add new symbols.
+  * [d53724e] Add install/postinstall files for ceph-mgr-k8sevents
+  * [acada37] Add lintian override for .chm file.
+    Source and build info is shipped.
+  * [bbb0bd6] copy the radosgw init file in override_dh_installinit.
+  * [a5958d5] Avoid duplicate files.
+    etc/bash_completion.d/ceph was accidentally shipped in ceph-base again.
+  * [fbc33a3] Add missing > in Dependency.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Thu, 02 Jan 2020 10:52:50 +0100
+
+ceph (14.2.4-9) unstable; urgency=medium
+
+  * [8c74414] lower --max-parallel for >=16GB
+    g++ loves to eat ram
+  * [b15dcdd] Build-dep. on python3-dev instead python3-all-dev.
+    Thanks to Graham Inggs (Closes: #948021)
+  * [c98ea07] Install bash-completion in /usr again.
+    This change went missing somewhere during the import of the
+    changes done in Ubuntu between 12.2.11 and 14.2.4.
+    Thanks to Andreas Beckmann (Closes: #948165)
+  * [c7d90b9] Move manpages to ceph-common again.
+    This also went missing during the import.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 05 Jan 2020 00:22:21 +0100
+
+ceph (14.2.4-8) unstable; urgency=medium
+
+  * [e187e6a] Use WITH_CCACHE from cmake to build with ccache.
+  * [8cbe25e] Hack CMakeCache.txt to disable HAVE_ARM_NEON on armel.
+    after trying to patch the various places where HAVE_ARM_NEON is used
+    the easiest way to get rid of it on armel seems to be to patch the cmake
+    cache file.
+  * [424ea9b] Don't build ceph on mipsel.
+    No compiler is able to build the code without running into oom-issues.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Wed, 01 Jan 2020 19:29:48 +0100
+
+ceph (14.2.4-7) unstable; urgency=medium
+
+  * [9b97753] Make sure we use ccache if needed.
+  * [3dbd1ac] d/rules: Remove the armel fpu options.
+    Hopefully properly patched now.
+  * [da253e4] m68k, sh4: build with clang to avoid gcc OOM.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Tue, 31 Dec 2019 00:13:23 +0100
+
+ceph (14.2.4-6) unstable; urgency=medium
+
+  * [b1c9b5d] Try to reduce memory usage even further if needed.
+    gcc loves to eat too much memory on armhf mipsel armel.
+  * [d695778] Remove softfp patch in favour of build flags.
+    This hopefully avoids the need to update and debug the patch.
+  * [6eddb32] Build with clang(++) on armhf mipsel armel.
+    Reports seem to suggest that clang does not need as much memory as gcc.
+  * [b9420ba] Fix unsigned/size_t issue for sh4 & m86k.
+  * [0027181] Updating changelog
+  * [6502f60] Fix another 32bit size_t/uint64_t issue.
+    In src/common/options.cc line 192.
+  * [4a0b044] Fix another 32bit size_t/uint64_t issue.
+    This time: powerpc.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sun, 29 Dec 2019 17:38:10 +0100
+
+ceph (14.2.4-5) unstable; urgency=medium
+
+  [ Bernd Zeimetz ]
+  * [453eaa4] Avoid using make -j 32 on powerpc.
+    A lot of CPU do not always help.
+
+  [ Milan Kupcevic ]
+  * [c6ec924] cherry pick critical bluestore data corruption fix
+    (Closes: #947457)
+
+  [ Bernd Zeimetz ]
+  * [e88fc21] Set -DWITH_BOOST_CONTEXT=OFF where necessary.
+    [!s390x !mips64el !ia64 !m68k !ppc64 !riscv64 !sh4 !sparc64 !x32]
+
+ -- Bernd Zeimetz <bzed@debian.org>  Sat, 28 Dec 2019 15:54:51 +0100
+
+ceph (14.2.4-4) unstable; urgency=medium
+
+  * [b70efb1] Create missing directories for arch:all build.
+  * [3e4530f] try to save even more memory on armhf.
+    Don't build debug flags at all.
+  * [b478ee5] Avoid overloading on mipsel.
+    Add mipsel to debian/patches/32bit-avoid-overloading.patch
+  * [85eb6e9] Also build jerasure with softfp on armel.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Thu, 26 Dec 2019 16:03:51 +0100
+
+ceph (14.2.4-3) unstable; urgency=medium
+
+  [ Bernd Zeimetz ]
+  * [f3f47f5] CI: disable extra-long running tests.
+
+  [ Steve Langasek ]
+  * [9794fc4] Drop uninstallable and unneeded server binaries on i386.
+    (Closes: #947156)
+
+  [ Bernd Zeimetz ]
+  * [6c2993f] Merge tag 'debian/14.2.4-0ubuntu3' into debian/unstable
+  * [0c5b41f] Use a tracker.d.o list instead of a closed one. (Closes: #760538)
+  * [d95db97] Try to build with --max-parallel=1 on slow arches.
+    We run into out-of-memory errors again.
+  * [e8d9e63] Use -mfloat-abi=softfp on armel for NEON instructions.
+    And again, that patch went missing somewhere.
+    Taken from
+    https://salsa.debian.org/ceph-team/ceph/commit/fa7d0d84f736d0b8450572f3192a43ff7b3252c4
+
+ -- Bernd Zeimetz <bzed@debian.org>  Tue, 24 Dec 2019 13:03:45 +0100
+
+ceph (14.2.4-2) unstable; urgency=medium
+
+  [ Thomas Goirand ]
+  * [4b2327d] Add a python3-ceph metapackage.
+
+  [ Bernd Zeimetz ]
+  * [dbc7d2f] Add lintian override for empty python3-ceph package.
+  * [5381390] Remove -en from description.
+    We actually want to have a description in the changes file...
+  * [4a57f31] Make python3-ceph dependencies binNMU safe
+
+ -- Bernd Zeimetz <bzed@debian.org>  Thu, 28 Nov 2019 09:43:37 +0100
+
+ceph (14.2.4-1) unstable; urgency=medium
+
+  * Uploading 14.2.4 to Debian.
+    (Closes: #936282, #943961, #940854, #942733)
+  * Adding missing sources (two.js, bootstrap 3.3.4)
+  * ceph-mon.postinst missed the interpreter
+  * Add missing dependency on python3
+  * ignore lintian errors about minified js with shipped sources
+  * Radowgw uses a systemd template, override lintian.
+  * libcephfs-jni: rpath to java libraries needed.
+    Add lintian override.
+  * Remove .pc folder from debian folder.
+  * Adding myself to Uploaders
+  * Merging the work done in Ubuntu.
+
+      [ Dariusz Gadomski ]
+      * d/p/issue37490.patch: Cherry pick fix to optimize LVM queries in
+        ceph-volume, resolving performance issues in systems under heavy load
+        or with large numbers of disks (LP: #1850754).
+    
+      [ James Page ]
+      * d/p/issue40114.patch: Cherry pick endian fixes to resolve issues
+        using Ceph on big-endian architectures such as s390x (LP: #1851290).
+      * New upstream release (LP: #1850901):
+        - d/p/more-py3-compat.patch,ceph-volume-wait-for-lvs.patch,
+          ceph-volume-wait-for-lvs.patch: Drop, included upstream.
+        - d/p/bluefs-use-uint64_t-for-len.patch: Cherry pick fix to resolve
+          FTBFS on 32 bit architectures.
+      * d/rules: Disable SPDK support as this generates a build which
+        has a minimum CPU baseline of 'corei7' on x86_64 which is not
+        compatible with older CPU's (LP: #1842020).
+      * d/p/issue40781.patch: Cherry pick fix for py3 compatibility in ceph-
+        crash.
+    
+      [ Eric Desrochers ]
+      * Ensure that daemons are not automatically restarted during package
+        upgrades (LP: #1840347):
+        - d/rules: Use "--no-restart-after-upgrade" and "--no-stop-on-upgrade"
+          instead of "--no-restart-on-upgrade".
+        - d/rules: Drop exclusion for ceph-[osd,mon,mds] for restarts.
+    
+      [ Jesse Williamson ]
+      * d/p/civetweb-755-1.8-somaxconn-configurable*.patch: Backport changes
+        to civetweb to allow tuning of SOMAXCONN in Ceph RADOS Gateway
+        deployments (LP: #1838109).
+    
+      [ James Page ]
+      * d/p/ceph-volume-wait-for-lvs.patch: Cherry pick inflight fix to
+        ensure that required wal and db devices are present before
+        activating OSD's (LP: #1828617).
+    
+      [ Steve Beattie ]
+      * SECURITY UPDATE: RADOS gateway remote denial of service
+        - d/p/CVE-2019-10222.patch: rgw: asio: check the remote endpoint
+          before processing requests.
+        - CVE-2019-10222
+        - Closes: #936015
+    
+      [ James Page ]
+      * New upstream release.
+      * d/p/fix-py3-encoding-fsid.patch: Drop, no longer required.
+      * d/p/pybind-auto-encode-decode-cstr.patch: Drop, reverted upstream.
+      * d/p/fix-py3-encoding-fsid.patch: Cherry pick correct fix to resolve
+        FSID encoding issues under Python 3 (LP: #1833079).
+      * d/p/pybind-auto-encode-decode-cstr.patch: Cherry pick fix to ensure
+        that encoding/decoding of strings is correctly performed under
+        Python 3 (LP: #1833079).
+    
+      * New upstream release.
+      * d/p/misc-32-bit-fixes.patch: Drop, included upstream.
+      * d/p/py37-compat.patch: Drop, included upstream.
+      * d/p/collections.abc-compat.patch: Drop, included in release.
+      * d/p/*: Refresh.
+      * d/*: Re-sync packaging with upstream for Nautilus release.
+      * d/control,ceph-test.*,rules: Disable build of test binaries, drop
+        ceph-test binary package (reduce build size).
+      * d/control,rules: Use system boost libraries (reduce build time).
+      * d/control: Add dependency on smartmontools, suggest use of nvme-cli
+        for ceph-osd package.
+      * d/p/32bit-*.patch: Fix misc 32 bit related issues which cause
+        compilation failures on armhf and i386 architectures.
+      * d/control: Add Breaks/Replaces on ceph-common for ceph-argparse to
+        deal with move of Python module.
+    
+      * New upstream release (LP: #1810766).
+      * d/p/*: Refresh.
+    
+      * d/p/more-py3-compat.patch: Add more py3 fixes.
+    
+      * d/p/more-py3-compat.patch: Misc Python 3 fixes in ceph-create-keys.
+    
+      * d/tests/python-ceph: Fix python3 test support resolving
+        autopkgtest failure.
+    
+      * New upstream point release.
+      * d/p/*: Refresh.
+      * d/control,python-*.install,rules: Drop Python 2 support.
+      * d/tests: Update for Python 2 removal.
+      * d/p/misc-32-bit-fixes.patch: Update type of rgw_max_attr_name_len,
+        resolving SIGABRT in radosgw (LP: #1805145).
+      * d/p/boost-py37-compat.patch: Fix compilation issue with boost
+        imports conflicting with ceph's assert.h header.
+      * d/p/collections.abc-compat.patch: Selective cherry-pick of upstream
+        fix for future compatibility with Python 3.8, avoiding deprecation
+        warnings under Python 3.7.
+    
+      * d/ceph-mds.install: Install missing systemd configuration
+        (LP: #1789927).
+    
+      * Re-instate 32bit architectures.
+        - d/control: Switch back to linux-any
+        - d/p/misc-32-bit-fixes.patch: Misc fixes for compilation
+          failures under 32 bit architectures.
+        - d/rules: Disable SPDK integration under i386.
+      * Repack upstream tarball, excluding non-DFSG sources (LP: #1750848):
+        - d/copyright: Purge upstream tarball of minified js files, which
+          are neither shipped in binaries or required for package build.
+        - d/watch: Add dversionmangle for +dfsg\d version suffix.
+      * d/control,rules: Drop requirement for gcc-7 for arm64.
+      * d/ceph-osd.udev: Add udev rules for sample LVM layout for OSD's,
+        ensuring that LV's have ceph:ceph ownership (LP: #1767087).
+    
+      * d/copyright,source.lintian-overrides: Exclude jsonchecker component
+        of rapidjson avoiding license-problem-json-evil non-free issue.
+      * New upstream point release.
+      * d/control: Remove obsolete X{S}-* fields.
+    
+      * New upstream release.
+      * Sync with changes in upstream packaging:
+        - d/*.install,rules: Use generated systemd unit files for install
+        - d/ceph-test.install: Drop binaries removed upstream.
+      * d/p/*: Refresh and drop as needed.
+      * d/*.symbols: Refresh for new release.
+      * d/rules,calc-max-parallel.sh: Automatically calculate the maximum
+        number of parallel compilation units based on total memory.
+      * d/control: Drop support for 32 bit architectures.
+      * d/control: Update Vcs-* fields for Ubuntu.
+      * d/control: Drop min python version field.
+
+ -- Bernd Zeimetz <bzed@debian.org>  Mon, 18 Nov 2019 14:18:10 +0100
+
+ceph (12.2.11+dfsg1-2.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * [3194010] Install ceph-volume@.service into ceph-osd.
+    (Closes: #924061)
+
+ -- Bernd Zeimetz <bzed@debian.org>  Fri, 05 Apr 2019 15:12:52 +0200
+
+ceph (12.2.11+dfsg1-2) unstable; urgency=medium
+
+  * [27a321] Fix builds on 32bit architectures
+  * [346bfa] Fix linking radosgw without BEAST frontend
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Tue, 19 Feb 2019 08:50:12 +0100
+
+ceph (12.2.11+dfsg1-1) unstable; urgency=medium
+
+  * [8b6f70] Build depend on cmake >= 3.13.2
+  * [98ed84] New upstream version 12.2.11+dfsg1
+    - Fixes CVE-2018-14662, CVE-2018-16889, CVE-2018-16846
+    (Closes: #921948, #918969, #921947)
+  * [4d5c86] Mark all LTTng tracepoints symbols as optional
+  * [b92a2f] Build depend on debhelper >= 11.5.4~
+  * [5781cc] Add Breaks/Replaces ceph-base (<< 12.2.10+dfsg1-1~) to ceph-common
+    (Closes: #919898)
+  * [658bd3] Fixup Breaks/Replaces for files moved between binary packages
+  * [c24137] Install systemd units for Ceph MGR service
+    (Closes: #920176, #919871)
+  * [091e14] Remove no longer needed dpkg-maintscript-helper calls in radosgw
+  * [567dde] Temp changelog commit
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Tue, 12 Feb 2019 10:55:02 +0100
+
+ceph (12.2.10+dfsg1-1) unstable; urgency=medium
+
+  * [22146e] ceph-base: create directory for bootstrap-rbd key
+  * [8c0362] Move ceph bash completion to ceph-common
+  * [b9e790] Make ceph binary package Suggests binNMU safe
+  * [fc83f1] Install ceph-fuse systemd service file
+  * [e70c29] Enable LTTng
+  * [234e9b] New upstream version 12.2.10+dfsg1
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sat, 29 Dec 2018 22:08:52 +0100
+
+ceph (12.2.8+dfsg1-5) unstable; urgency=medium
+
+  * [66e03b] Fix linking on archs which require libatomic
+  * [5236f3] Fix Python 3 autopkgtest
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Fri, 30 Nov 2018 16:49:02 +0100
+
+ceph (12.2.8+dfsg1-4) unstable; urgency=medium
+
+  * [97dfb6] Fix detection of armel for NEON instructions (Closes: #913599)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Thu, 22 Nov 2018 22:33:21 +0100
+
+ceph (12.2.8+dfsg1-3) unstable; urgency=medium
+
+  * [168bb9] Build depend on pythonX-dev instead of pythonX-all-dev
+    (Closes: #912905)
+  * [38d140] Patch to check for atomic support during build (Closes: #913601)
+  * [9cc0a5] Disable libboost-context on unsupported archs (Closes: #913600)
+  * [238143] Fix building with -g1 on 32 bit architectures
+  * [3b48d5] Use -mfloat-abi=softfp on armel for NEON instructions
+    (Closes: #913599)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Thu, 22 Nov 2018 09:38:09 +0100
+
+ceph (12.2.8+dfsg1-2) unstable; urgency=medium
+
+  * [5c4b36] Add architecture specific symbols for librados2
+  * [38bc1b] Build depend on libatomic1 on armel, m68k, mips, mipsel, powerpc,
+    powerpcspe and sh4
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Wed, 31 Oct 2018 23:38:05 +0100
+
+ceph (12.2.8+dfsg1-1) unstable; urgency=medium
+
+  [ James Page ]
+  * [9c55f6] Ensure that systemd targets are enabled and started
+  * [17ca38] Support optional runtime loading of openssl in radosgw
+  * [8927f6] Fix build on i386
+  * [774281] Ensure all ceph modules are included in the binary package.
+  * [00ca38] Cherry pick upstream fix to resolve FTBFS on armhf
+  * [5d118c] Add ceph-volume tools to ceph-osd package
+
+  [ Gaudenz Steinlin ]
+  * [ba768b] New upstream version 12.2.8+dfsg1
+    (Closes: #852999, #864535, #893149)
+  * [96eba0] Exclude jsonchecker from upstream source
+  * [7dd73b] Exclude Windows help file from orig tarball
+  * [9d1740] Move ceph initscript and systemd target to ceph-base
+  * [8ae049] Drop obsolete patches (either upstreamed or because of switch to
+    cmake)
+  * [cfd095] Create directory for Ceph Manager bootstrap keys
+  * [858194] Shell scripts moved out of architecture specific directory
+  * [f3f387] Merge ceph-fs-common package into ceph-common
+  * [1789fe] Move radosgw-admin to ceph-common
+  * [6f51fc] Add compressor plugins to ceph-common
+  * [5bd0d1] Add crypto plugins to ceph-common on amd64
+  * [7724f7] New binary package ceph-mgr
+  * [804be6] Remove librgw_file* from ceph-test. These are unit tests for rgw.
+  * [fef87a] Upstream changed the build system to cmake
+  * [134e34] Build Python 3 versions of Python modules (Closes: #883148)
+  * [52a969] Package RADOS gateway Python bindings (python-rgw)
+  * [a48700] Add rados-objclass-dev binary package
+  * [30306f] Update dependencies for new upstream release
+  * [4d07af] libcephfs1 -> libcephfs2 soname bumped upstream
+  * [c4022d] Remove ceph-disk-udev no longer shipped upstream
+  * [9aad82] Remove sample.fetch_config no longer shipped upstream
+  * [fc163d] Update copyright for new upstream release
+  * [21c408] Add /usr/bin/radosgw-es to radosgw package
+  * [f63b2a] ceph-osd: sysctl config to increase the maximum number of AIO
+    requests
+  * [5f1f30] Remove static libraries from -dev packages (removed upstream)
+  * [1db168] Install ceph-detect-init into /usr/bin instead of /usr/sbin
+  * [1fb625] Move ceph-*-tool from ceph-test into daemon packages
+  * [6a5628] Remove obsolete X-Python-Version
+  * [c6fc4d] Update to Debian Policy version 4.2.1
+  * [5821f2] Set multiarch triplet in debian/rules
+  * [9bdce0] Remove override of dh_auto_install
+  * [b71c87] Add libceph-common to librados2 and librados-dev pkgs
+  * [c746ce] Override JSON license lintian warning (false positive, code
+    removed)
+  * [17d65c] Use dh_missing instead of dh_install --list-missing
+  * [cba40a] Remove obsolete ceph-create-keys@.service from ceph-base
+  * [252d62] Update watch file for https and repacking
+  * [ef84cf] Install ceph SysV init script with dh_install
+  * [0c2c84] Add missing source for jquery.flot.js (actually
+    jquery.colorhelpers.js)
+  * [0f1e2e] Add missing sources for JQuery in civetweb
+  * [8010cc] Add missing sources from AdminLTE
+  * [7c425a] Add lintian overrides for source-is-missing false positives
+  * [75f7d2] Update symbols files
+  * [aa7295] Patch to fix build failures with Boost 1.67
+  * [872887] Build with Debian packaged Boost 1.67 instead of bundled Boost 1.66
+  * [abf215] Use google-perftools on all supported architectures
+  * [44dd74] Add missing sources for the mgr dashboard plugin
+  * [39f9d6] Depend on ceph-common for pythonX-cephfs (Closes: #896400)
+  * [7a10f0] Improve autopkgtests
+  * [7515ca] Add Python dependency for ceph-fuse
+  * [d91a92] Make the ceph binary package a pure metapackage
+  * [df1dc8] Add Lintian overrides for systemd targets
+  * [bc7cd4] Install upstream rbdmap systemd service file
+  * [dd44ea] Remove RUNPATH from JNI libraries
+  * [7e9bce] Add dependency on junit4 and libcephfs-java for ceph-test
+  * [dfbe7c] Remove unnecessary ceph-base postrm script (Closes: #867465)
+  * [bad29d] Fix permissions on /var/run/ceph in SysV init script
+    (Closes: #869142)
+  * [6edc7a] Mark libraries as Multi-Arch compatible (Closes: #822740)
+
+  [ Thomas Goirand ]
+  * [46be6f] Change VCS links to point to Salsa
+
+  [ Shengjing Zhu ]
+  * [bfcf95] Don't treat rados-classes and ceph/compressor as shared libraries
+  * [0ef4f9] Add missing interpreter in ceph-{osd,mon}.postinst
+    (Closes: #862684, #862685)
+  * [f75bfc] Change section of libcephfs-jni from libs to java
+  * [f4b5d4] Add patch to fix various spelling errors
+  * [f60d21] Patch to remove link to ceph.com for dashboard favicon
+  * [116028] Backport test build fix from upstream
+  * [b83166] Change architecture of ceph-fuse to linux-any (from amd64)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sun, 28 Oct 2018 23:43:10 +0100
+
+ceph (10.2.5-7.2) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Build with -g1 instead of -g on 32bit architectures to fix
+    FTBFS due to the 2GB/3GB address space limits.
+
+ -- Adrian Bunk <bunk@debian.org>  Wed, 07 Jun 2017 11:39:39 +0300
+
+ceph (10.2.5-7.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * ceph-mon: Add Breaks+Replaces: ceph-common (<< 10) for taking over
+    /usr/bin/ceph-rest-api.  (Closes: #864161)
+
+ -- Andreas Beckmann <anbe@debian.org>  Tue, 06 Jun 2017 09:08:30 +0200
+
+ceph (10.2.5-7) unstable; urgency=medium
+
+  * [a12798] Add fix-init-system-detection.patch.
+    This replaces the static init system detection by more appropriate
+    runtime detection which also works if a Debian system is running with
+    sysvinit. (Closes: #862075)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Fri, 12 May 2017 12:12:00 +0200
+
+ceph (10.2.5-6) unstable; urgency=medium
+
+  * [e44a30] Disable running dh_auto_install with parallel jobs
+    (Closes: #850906)
+  * [b7e926] Link with libatomic and --as-needed on all archs (Closes: #850831)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Wed, 11 Jan 2017 12:02:24 +0100
+
+ceph (10.2.5-5) unstable; urgency=medium
+
+  * [f4675d] Set _FILE_OFFSET_BITS=64 via DEB_CPPFLAGS_MAINT_APPEND
+  * [f60392] Install systemd targets for ceph services (Closes: #850509)
+  * [d3baba] Link with -latomic on mips/mipsel
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Tue, 10 Jan 2017 09:04:48 +0100
+
+ceph (10.2.5-4) unstable; urgency=medium
+
+  * [88034c] Build with ggc-min-expand=5 on mips/el (Closes: #849657)
+  * [c31e79] Add patch to build rocksdb with -latomic on mips/el
+  * [182dd8] NEWS entry about upgrades from Debian Jessie
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sun, 08 Jan 2017 21:34:21 +0100
+
+ceph (10.2.5-3) unstable; urgency=medium
+
+  * [eeff8d] Use -mfloat-abi=softfp on armel for NEON plugin (Closes: #849660)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Thu, 05 Jan 2017 09:18:41 +0100
+
+ceph (10.2.5-2) unstable; urgency=medium
+
+  * [3859ca] Only list missing files instead of failing (Closes: #849031)
+  * [47aef7] Remove chrony from recommends (provides time-daemon)
+    (Closes: #827673)
+  * [3e96e6] Upstream fix for CVE-2016-9579 (short CORS request)
+    (Closes: #849048)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sat, 24 Dec 2016 13:14:28 +0100
+
+ceph (10.2.5-1) unstable; urgency=medium
+
+  [ James Page ]
+  * [754935] Imported Upstream version 9.2.0
+    - [df85c3] Resync relevant packaging changes with upstream.
+    - [be5f82] Refresh patches.
+    - [d1f3fe] Add python-setuptools to BD's for ceph-detect-init.
+    - [b2f926] Add lsb-release to BD's to ensure that python modules are
+               installed to correct locations.
+    - [e4d702] Add python-sphinx to BD's to ensure man pages get generated
+               and installed.
+    - [3ead6e] Correct install location for ceph-monstore-update tool.
+    - [269754] [177b7a] Update symbols for new release.
+  * [4c45629] Update NEWS file for infernalis changes.
+  * [940491e] Limit number of parallel builds to 2 to reduce memory footprint
+              on builders.
+  * [a2bed4] New upstream version 10.2.5
+  * [cbfb6b] Sync upstream changes around rbdmap install and conf files.
+  * [5c52b2] Drop patches include upstream, refresh remaining patches
+  * [8f9820] Resync further packaging changes from upstream.
+  * [27d010] Re-add bz2-dev BD.
+  * [e94aa2] Add python-dev to BD's.
+  * [e201c1] d/p/pybind-flags.patch: Ensure that cython *FLAGS are correctly
+             set.
+  * [14cd12] d/p/fix-systemd-escaping.patch: Ensure that leading '/' is stripped
+             from block device paths when escaping for use in systemd unit
+             names.
+  * [33b3aa] Add pull request for systemd fixes
+  * [d781a2] d/ceph-mds.postinst: Fix syntax error.
+  * [ce55ec] Ensure that python flags are correct set for cython rbd build.
+  * [f9e35b] Switch rbd python binding to cython
+  * [8ccae0] Drop ceph_volume_client until its actually in the codebase.
+  * [0a11cc] Install to relative, not absolute /etc/ceph.
+  * [e2415a] Drop ceph-bluefs-tool from ceph package.
+  * [b16966] d/ceph-mds.dirs: Actually create /var/lib/ceph/mds prior to
+             changing permissions (LP: #1544647).
+  * [0c1201] d/ceph.init: Restore link to init-ceph, resolving un-install
+             failures due to missing init script (LP: #1546112).
+
+  [ Gaudenz Steinlin ]
+  * [e3cb86] Remove no longer needed check for dh-autoreconf>=6
+  * [9157b3] Install ceph-brag tool into ceph-common
+  * [74e9f3] Install ceph-client-debug into ceph-test
+  * [476449] Use jh_installlib to install Java libraries
+  * [643f6e] Don't remove configure script on dh_clean
+  * [4bb203] Remove *.la files to binary packages
+  * [b76c6a] Add build dependency on libboost-iostreams-dev
+  * [b0506f] Add build dependency on libldap2-dev
+  * [526ce1] Refresh patches for upstream version 10.2.2
+  * [cf539b] Remove patches applied upstream in 10.2.2
+
+  [ James Page ]
+  * [646b48] Add librgw2 binary packages.
+  * [ae3cda] Drop virtualenv from BD's.
+  * [7ebd7c] Re-enable rocksdb build for bluestore support
+  * [e393e5] Add rbd-mirror package
+  * [00d06d] Add patch to set g++ flags correctly for rocksdb
+  * [3b029a] Enable gperftools on arm64 architecture.
+  * [e88620] Add ceph-bluefs-tool to install.
+  * [4f40a1] Add s390x to list of rocksdb flags
+  * [858334] d/p/skip-setup.py-makefiles.patch,rules: Avoid use of virtualenv to
+             install ceph-disk and ceph-detect-init python modules.
+  * [40c3b7] Update watch file to scan for gz files.
+  * [49b716] Install librgw_file* as part of ceph-test package.
+
+  [ Gaudenz Steinlin ]
+  * [2e156d] d/rules: Install upstart and systemd configurations for rbd-mirror.
+
+  [ James Page ]
+  * [ca0b07] d/copyright: Ensure that jerasure and gf-complete are not stripped
+             from the upstream release tarball.
+  * [eee861] d/p/disable-openssl-linking.patch: Disable build time linking with
+             OpenSSL due to licensing incompatibilities.
+  * [07d7a6] d/*.symbols: Add new symbols for RC.
+  * [2416f1] Fix multiarch paths for librgw
+  * [2da01d] d/rules: Ensure that dh_systemd_start does not insert maintainer
+             script snippets for ceph-mon and ceph-create-keys - service restart
+             should be handled outside of the packaging as it is under upstart
+             and for all other systemd unit files installed (LP: #1563330).
+  * [76ec3b] d/ceph-common.postinst: Silence output of usermod call
+             (LP: #1569249).
+  * [63d60f] d/rules,ceph-common.install: Ensure that /etc/default/ceph is a
+             file and not a directory (LP: #1587516).
+  * [a866ca] d/control: Bumped Standards-Version to 3.9.8, no changes.
+  * [e0811e] d/ceph.install: Drop install of 60-ceph-partuuid-workaround.rules
+             as no longer part of upstream codebase.
+  * [25954f] * d/*: Split ceph-osd and ceph-mon into separate binary packages
+             and add new ceph-base binary package inline with upstream packaging
+             changes (LP: #1596063).
+  * [f1287b] Add missing misc depends
+  * [afeb18] d/rules,control: Drop -dbg packages and let Ubuntu builds take care
+             of ddeb generation instead.
+
+  [ Gaudenz Steinlin ]
+  * [e133b2] Install rbdmap manpage into ceph-common
+  * [b5ed64] Install radosgw-token command into radosgw package
+  * [0a27e1] Adapt installation of Python files to latest Jewel release
+  * [ef2544] Drop rocksdb-flags patch applied upstream
+  * [07aef4] Add patch osd-limit-omap-data-in-push-op
+  * [32f14d] Refresh patches
+
+  [ James Page ]
+  * [f4e95c] Fix install location for mount.ceph
+  * [6a9efc] Update install location for mount.ceph.fuse
+  * [7624f0] d/control: Add Pre-Depends on ceph-common to ceph-osd to ensure
+             that ceph user and group are created prior to unpacking of udev
+             rules (LP: #1631328).
+
+  [ Frode Nordahl ]
+  * [022ee3] Add rgw_rados-creation_time patch
+
+  [ Gaudenz Steinlin ]
+  * [05225f] Remove old ceph logrotate configuration
+
+  [ James Page ]
+  * [cfa82f] Refresh patches
+
+  [ Gaudenz Steinlin ]
+  * [fdec5d] Remove upstart support
+  * [5966f0] Use new RSA key for ceph-post-file
+  * [3748b6] Only install ChangeLog as upstream changelog
+  * [aa1393] Install bash-completions to /usr
+  * [ab0a88] Dependency on lsb-base for initscripts
+  * [4d78b0] Disable radosgw SysV init script on systemd
+  * [ff4c87] Don't install python bytecode
+  * [3cbf5e] Add missing dependencies on python (ceph-osd, ceph-mon)
+  * [8c70df] Update symbols files (librados2 and librbd1)
+  * [e4f0c5] Add ${shlibs:Depends} on Python C extensions
+  * [936838] Allow setting the number of parallel jobs
+  * [e5aa0f] Remove Laszlo and add myself to uploaders
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Sat, 17 Dec 2016 22:40:22 +0100
+
+ceph (0.94.5-1) experimental; urgency=medium
+
+  * [2d330d6] New upstream release:
+    - [1e93090] Drop patch for CVE-2015-5245, included upstream.
+    - [20adc7d] Refresh all other patches.
+  * [9255e5d] Ensure any erasure coding test libraries and dangling symlinks
+              are not included in the ceph package.
+
+ -- James Page <james.page@ubuntu.com>  Mon, 09 Nov 2015 12:09:51 +0000
+
+ceph (0.94.3-1) experimental; urgency=medium
+
+  * [580fef] Imported Upstream version 0.94.3 (Closes:  #777814, #795178)
+  * [536935] Add upstream patch to fix CVE-2015-5245 (Closes: #798567)
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Fri, 18 Sep 2015 16:55:23 +0200
+
+ceph (0.94.2-2) experimental; urgency=medium
+
+  * Revert "Drop virtualenv BD, disable unit tests."
+  * Restore patches for test enablement.
+  * Display test-suite log output in the event of failures.
+
+ -- James Page <james.page@ubuntu.com>  Mon, 20 Jul 2015 13:37:06 +0100
+
+ceph (0.94.2-1) experimental; urgency=medium
+
+  * Resync with Ubuntu, introducing Ceph Hammer stable release:
+    - d/*.symbols: Update inline with upstream additions, use regex
+      for ceph version symbol.
+    - d/lib-systemd/system/ceph-create-keys.service: Automatically create
+      admin and bootstrap keys after ceph mon startup.
+    - d/p/vivid-does-systemd.patch: Ensure that disks prepared on vivid
+      or later use systemd for init.
+    - d/lib-systemd/system/*.service: Align nofile limits and restart config
+      with equivalent upstart configurations.
+    - d/p/fix-cycles-arch.patch: Skip initialization of cycles_per_sec
+      if rtdsc (or equivalent) is not supported.
+    - d/ceph{-common}.install,control: Move ceph_argparse.py down into
+      ceph-common package to fixup ceph cli usage/autopkgtest failure.
+    - d/control,ceph-common.install,librbd1.install: Move rbdnamer and
+      associated udev rules into ceph-common package.
+    - d/control,python-*: Split out rbd, rados and cephfs bindings into
+      separate python packages, move some bits into ceph/ceph-common.
+    - d/control: Move python-flask dependency to ceph package, only required
+      for REST API.
+    - d/control: Use google-perftools on arm64.
+    - d/control: Re-order Recommends to prefer ntp over chrony for Ubuntu.
+    - d/p/ceph-osd-prestart-path.patch: Fixup path for ceph-osd upstart
+      configuration pre-start script.
+    - d/p/fix-argparse-defaults.patch: Workaround behavioural change in
+      argparse set_defaults in python 2.7.9
+  * New upstream point release:
+    - d/p/*: Refresh.
+  * d/p/use_system_jerasure.patch,d/control: Drop use of libjerasure
+    as the patch is intrusive and expensive to maintain; will revisit if
+    adopted upstream.
+
+ -- James Page <james.page@ubuntu.com>  Tue, 16 Jun 2015 11:31:05 +0100
+
+ceph (0.87-2) experimental; urgency=low
+
+  * Team upload.
+
+  [ Gaudenz Steinlin ]
+  * README.Debian: added clarification about setting the hashpspool flag.
+    (Closes: #769596).
+
+  [ James Page ]
+  * Added new "modules.patch" to mark new erasure coding libraries as
+    modules, wildcard install.
+
+  [ Dmitry Smirnov ]
+  * Recommends: added "ntp" to list of time-daemon alternatives
+    (Closes: #767511).
+  * Introduced native systemd services (except "rbdmap"), (Closes: #769593).
+  * ceph-test: install forgotten files.
+  * Run post-build tests:
+    + updated "virtualenv-never-download.patch" to pass
+      "--system-site-packages" to virtualenv to prevent downloads.
+    + added new patches to disable network-dependent and failing tests.
+  * Patchworks:
+    - bug-9341.patch
+    + bug-10036.patch (to show OSD affinity in "ceph osd tree").
+      Thanks, Mykola Golub.
+    + bug-10059.patch
+    + 0latest-giant.patch (Last-Update: 2014-11-15).
+    + sleep-recover.patch
+    + tests-disable.patch (to disable tests that need cluster).
+    + tests-disable-ceph-disk.patch
+    + use_system_gtest.patch (commented)
+      as first attempt to build with system "libgtest-dev".
+    + use_system_jerasure.patch
+  * Build-Depends:
+    + libjerasure-dev (>= 2.0.0-2~)
+    + virtualenv
+    + valgrind [amd64 armhf i386 powerpc]
+  * rules: pass "--without-lttng" to explicitly disable "lttng" to avoid
+    auto-enable if found.
+  * rules: disabled bundled RocksDB:
+    RocksDB suppose to improve performance of keyvaluestore OSDs but the
+    latter slow down to nearly unusable state when filled over 1 TiB even with
+    RocksDB. Moreover KV backend is experimental and super dangerous -- I lost
+    cluster due to OSD poisoning caused by KV OSD which was plugged only
+    during limited time. LevelDB is good enough, for now I see no reason to
+    use RocksDB especially considering that it is not packaged separately.
+  * Removed myself from Uploaders.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Wed, 01 Apr 2015 11:47:38 +1100
+
+ceph (0.87-1) experimental; urgency=medium
+
+  * New major upstream release [October 2014].
+    + new "libradosstriper*" binary packages.
+  * Patchworks (removed old patches, refreshed remaining ones).
+    + "bug-9814.patch" to prevent OSD crash. Thanks, Haomai Wang.
+  * Install systemd sleep handler.
+  * Exclude erasure-code plugins from `dh_makeshlibs` processing to avoid
+    useless calls to `ldconfig` in maintainer scripts.
+  * Build-Depends:
+    + libbabeltrace-dev
+    + libbabeltrace-ctf-dev
+    + libbz2-dev
+    + libudev-dev
+    + zlib1g-dev
+  * Build with "--with-babeltrace".
+  * Build and statically link bundled RocksDB.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Thu, 30 Oct 2014 12:43:49 +1100
+
+ceph (0.80.9-2) unstable; urgency=medium
+
+  * [70fc1d] Add NEWS entry about CRUSH issues fixed in 0.80.9
+  * [f41bb6] Add NEWS entry about rbd backed filesystems and systemd
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Tue, 05 May 2015 21:29:15 +0200
+
+ceph (0.80.9-1) unstable; urgency=medium
+
+  * [4b4e] Imported Upstream version 0.80.9
+  * [7102] Remove patches firefly-latest and p2139 applied upstream
+  * [5869] Add myself to uploaders
+
+ -- Gaudenz Steinlin <gaudenz@debian.org>  Mon, 04 May 2015 08:49:37 +0200
+
+ceph (0.80.7-2) unstable; urgency=medium
+
+  * Team upload.
+  * Build-Depends: +libjerasure-dev (>= 2.0.0-2~)
+  * New patch to use system "jerasure" library instead of its bundled copy.
+  * Removed myself from Uploaders.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Thu, 11 Dec 2014 12:55:38 +1100
+
+ceph (0.80.7-1) unstable; urgency=medium
+
+  * New upstream release [October 2014].
+  * Minor update to long description of "rbd-fuse" (Closes: #765462).
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Thu, 16 Oct 2014 04:36:23 +1100
+
+ceph (0.80.6-1) unstable; urgency=medium
+
+  * New upstream release [October 2014].
+  * Standards-Version: 3.9.6.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Thu, 02 Oct 2014 23:07:04 +1000
+
+ceph (0.80.5-2) unstable; urgency=low
+
+  * Patchworks:
+    + new patch for Ceph#9341 to dramatically (e.g seconds instead of
+      hours) reduce rejoin (i.e. MDS restart) time (fuse clients).
+    + new "p2139.patch".
+    + new patch with fixes from Firefly HEAD;
+      includes patch to fix FTBFS on alpha (Closes: #756892).
+      updated "librbd1.symbols";
+  * Build-Depends: mark "yasm" as [amd64] (Closes: #760383).
+  * Recommends: + "time-daemon | chrony".
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Tue, 16 Sep 2014 03:54:15 +1000
+
+ceph (0.80.5-1) unstable; urgency=medium
+
+  * New upstream stable release:
+    - d/p/firefly-post-release.patch: Dropped, no longer required.
+    - d/lib{rados2,cephfs1}.symbols: Update with new symbols.
+
+ -- James Page <jamespage@debian.org>  Wed, 30 Jul 2014 10:15:40 +0100
+
+ceph (0.80.4-1) unstable; urgency=medium
+
+  * New upstream release [July 2014].
+  * New patches:
+    + rbdmap1-mount.patch
+    + rbdmap2-hooks.patch
+    + rbdmap3-lazyumount.patch
+    + bug-8821.patch
+  * radosgw: removed unused lintian overrides.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Fri, 18 Jul 2014 02:33:39 +1000
+
+ceph (0.80.1-2) unstable; urgency=low
+
+  * Megapatch from "firefly" branch with post-0.80.1 fixes.
+  * Patches for upstream bugs 8342, 8624 and some cherry-picks.
+  * New "bash-completion.patch" with Bash completion improvements.
+  * New patch to fix FTBFS on 'hppa' (Closes: #748571).
+  * "sample.ceph.conf.patch": minor update.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Sat, 05 Jul 2014 20:29:44 +1000
+
+ceph (0.80.1-1) unstable; urgency=low
+
+  * New upstream release [May 2014].
+  * Dropped all backported patches.
+  * New "sleep-recover" and "client-sleep[1,2,3]" patches to fix
+    fuse-client hang after resume from suspend [#8291]; thanks, Zheng Yan.
+  * New "gcj_search_path.patch" to find "jni.h" with gcj-jdk v4.9.0.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Wed, 14 May 2014 09:24:15 +1000
+
+ceph (0.80-1) unstable; urgency=low
+
+  * New upstream release [May 2014].
+    + upload to unstable.
+  * Updated "README.Debian".
+  * Updated "debian/copyright"; Thanks, László Böszörményi.
+  * Added backported patches:
+      [8113, 8175, 8282, 8291, bp0001, sample.ceph.conf].
+  * "gbp.conf": don't merge to experimental.
+  * lintian-overrides: spelling-error-in-binary * tEH the.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Wed, 07 May 2014 16:43:07 +1000
+
+ceph (0.80~rc1-1) experimental; urgency=low
+
+  * New upstream pre-release.
+  * Minor re-factoring of udev rules installation.
+  * ceph-common: added ceph-crush-location.1 man page.
+  * ceph-test-dbg: fixed Depends.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Thu, 24 Apr 2014 02:52:12 +1000
+
+ceph (0.79-3) experimental; urgency=low
+
+  * New "arch.patch" to detect build architecture using dpkg-architecture.
+  * Mark amd64-only symbols as such.
+  * Minor rules cleanup.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Sat, 19 Apr 2014 15:56:37 +1000
+
+ceph (0.79-2) experimental; urgency=low
+
+  [ James Page ]
+  * d/p/modules.patch,d/ceph.install: Mark all jerasure plugins as modules
+    and ensure they are all installed.
+
+  [ Dmitry Smirnov ]
+  * Patchworks:
+    - removed unused "defaults-leveldb-osd.patch".
+    + improved description of "modules.patch". Thanks, James Page.
+    + added new backported patches [#5469, #8008, _1606, spelling].
+  * Added .symbols and "dh_makeshlibs -V" shlibs tightening (Closes: #744382).
+  * README.Debian: added note regarding kernel client mount option.
+  * copyright: added license for man files.
+  * control: "Suggests: logrotate".
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Fri, 18 Apr 2014 18:27:01 +1000
+
+ceph (0.79-1) experimental; urgency=low
+
+  * New upstream release [April 2014].
+  * Tighten dependency on ceph-common.
+  * Install pm-suspend handler to stop/start ceph services on suspend/resume.
+  * New (inactive) patch to bump OSD's leveldb defaults.
+  * Patches dropped (applied-upstream):
+    - init.patch
+    - logrotate.patch
+    - fix-defaultweight.patch
+  * Refreshed "modules.patch".
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Tue, 08 Apr 2014 16:52:04 +1000
+
+ceph (0.78-2) experimental; urgency=low
+
+  * Standards to 3.9.5.
+  * debian/copyright: reviewed and updated.
+  * ceph-test: added lintian-override for "binary-without-manpage".
+  * Patchworks:
+    + refreshed/renamed/reordered "virtualenv-never-download.patch".
+    + new "init.patch" for init.d scripts lintianisation.
+    + new "logrotate.patch" to avoid rotating empty logs.
+    + new "fix-defaultweight.patch" to fix weight calculation on OSD start.
+    + new "gcj.patch" with partial fix to FTBFS with gcj-jdk.
+  * Use symlinks to simplify installation of init.d and logrotate scripts.
+  * Added retrospective changelog entry to mention new B-D "libblkid-dev".
+  * Added "debian/clean file".
+  * Added "README.Debian" file with some hopefully useful notes.
+  * Added "mount.fuse.ceph.8" man page.
+  * rules:
+    + "dh --with" optimised.
+    + set JAVAC to prevent FTBFS due to incorrect use of 'gcj', when detected.
+    + verbose mode for 'cp' and 'rm' commands.
+    + build with "--as-needed" to minimise needless linking.
+  * control:
+    + lintian/duplicate-short-description + consistent capitalisation.
+    + removed needless versioned dependencies from Build-Depends.
+    + added myself to Uploaders.
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Tue, 25 Mar 2014 07:17:40 +1100
+
+ceph (0.78-1) experimental; urgency=medium
+
+  * New upstream release:
+    - d/control: Add "xfslib-dev" and "libblkid-dev" to BD's.
+    - d/*: Sync relevant packaging changes from upstream.
+    - d/p/*: Drop upstreamed patches.
+    - d/p/modules.patch: Mark libcls_user.so and libec_jerasure.so as modules.
+    - d/ceph.install: Only install libec_jerasure.so.
+  * d/ceph-test.install: Install test binaries to /usr/lib/ceph/bin; they
+    really don't need to be installed on the default path.
+  * d/{ceph|radosgw|ceph-mds}.lintian-overrides: Add overrides for intentional
+    difference in naming and structure between upstart configurations and
+    init.d scripts.
+
+ -- James Page <james.page@ubuntu.com>  Sat, 22 Mar 2014 18:27:40 +0000
+
+ceph (0.72.2-3) unstable; urgency=medium
+
+  * Team upload.
+
+  [ James Page ]
+  * d/ceph-test.install: Install test binaries to /usr/lib/ceph/bin; they
+    really don't need to be installed on the default path.
+
+  [ Dmitry Smirnov ]
+  * Tightened shlibs with "dh_makeshlibs -V" (Closes: #679686).
+
+ -- Dmitry Smirnov <onlyjob@debian.org>  Mon, 14 Apr 2014 17:28:20 +1000
+
+ceph (0.72.2-2) unstable; urgency=medium
+
+  * d/radosgw.{postinst,postrm,preinst}: Handle renaming of radosgw
+    upstart configuration on upgrade@0.72.1-3.
+  * d/{ceph|ceph-mds|radosgw}.{postinst|prerm}: Check to ensure that system
+    is running upstart before trying to start/stop upstart configurations
+    (Closes: #734241, #738845, #738845).
+
+ -- James Page <jamespage@debian.org>  Sat, 08 Mar 2014 16:48:28 +0000
+
+ceph (0.72.2-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- James Page <james.page@ubuntu.com>  Wed, 01 Jan 2014 09:32:03 +0000
+
+ceph (0.72.1-3) unstable; urgency=low
+
+  * d/rules,ceph.install: Correct install paths for ceph-* helpers. 
+  * d/p/modules: Mark libcls_kvs.so as module.
+  * d/rules: Rename radosgw upstart configuration to radosgw-instance to
+    avoid namespace conflict with init script which breaks backwards
+    compatibility (LP: #1255464).
+
+ -- James Page <james.page@ubuntu.com>  Wed, 27 Nov 2013 10:52:48 +0000
+
+ceph (0.72.1-2) unstable; urgency=low
+
+  * Fix upgrade failures from ceph < 0.67.3-1 (Closes: #728164):
+    - d/control: ceph-mds Breaks/Replaces ceph (<< 0.67.3-1).
+    - d/control: ceph-fs-common Breaks/Replaces ceph-common (<< 0.67.3-1).
+  * d/rules,control: Use google-perftools on armhf and powerpc archs.
+
+ -- James Page <james.page@ubuntu.com>  Mon, 25 Nov 2013 10:13:19 +0000
+
+ceph (0.72.1-1) unstable; urgency=low
+
+  * New upstream stable release:
+    - d/ceph-test.install: Add new ceph_filestore_tool, ceph-kvstore-tool
+      and ceph_test_cls_hello binaries, drop ceph_test_store_tool.
+    - d/ceph-common.install: Add new ceph-post-file binary and manpage.
+    - d/ceph.install: Tweaked install path /usr/sbin -> /sbin.
+    - d/control: Add new BD's on python-nose and yasm.
+    - d/copyright: Updates inline with changes in codebase.
+    - d/ceph.install,rules: Install rbdmap init file using dh_installinit.
+    - Refresh patches.
+  * d/control,rules: Disable unit testing; it requires a forked version of
+    cram and is still trying to download dependencies using virtualenv.
+
+ -- James Page <james.page@ubuntu.com>  Fri, 22 Nov 2013 13:02:29 +0000
+
+ceph (0.67.3-1) unstable; urgency=low
+
+  [ Laszlo Boszormenyi ]
+  * New upstream release (Closes: #693866, #705262).
+  * Update debian/copyright.
+  * Sync with Ubuntu.
+
+  [ James Page ]
+  * d/control,rules,libcephfs-{java,jni}: Enable Java CephFS library,
+    add new BD's on javahelper and default-jdk, add dbg package.
+  * d/control: Add new BD on libboost-thread-dev for RADOS Gateway
+    keystone integration.
+  * d/{control,obsync.install}: Drop obsync package inline with
+    upstream.
+  * d/librbd-dev.install: Pickup new features.h file.
+  * Remove manual calls to ldconfig:
+    - d/lib{rados2|rbd1|cephfs1}.post*: Dropped - all these do is call
+      ldconfig which will automatically be done. 
+    - d/rules: Let dh_makeshlibs do its magic with postinst/postrm.
+  * d/tests/*: Added autopkgtests for librbd, librados, python-ceph
+    and the ceph CLI.
+  * d/control: Fix versions of librbd1, librados2 and libcephfs1 for
+    python-ceph as it requires an exact version match.
+  * d/ceph.docs: Drop - README from upstream is only useful for developers
+    (Closes: #722957).
+  * d/rules: Drop --upstart-only from dh_installinit calls for upstart
+    configurations; this is deprecated in Ubuntu and not support in Debian.
+  * d/rules: Exclude jni package from shlibs generation to avoid pointless
+    ldconfig calls in maintainer scripts.
+
+  [ Bastian Blank ]
+  * Use debhelper 9.
+  * Use dh-autoreconf.
+  * Install files from source tree if possible.
+  * Run test-suite:
+    - Build-depend on python-virtualenv.
+    - Ask virtualenv to never download anything.
+  * Fix clean target.
+  * Properly mark library modules:
+    - Don't longer exclude them from stripping.
+  * Drop all libtool .la files.
+  * Generate python dependencies.
+  * Don't exclude stuff from shlibs generation.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.org>  Tue, 01 Oct 2013 02:29:08 +0200
+
+ceph (0.48-1) unstable; urgency=low
+
+  * New upstream release, the first with long-term support.
+  * As gceph dropped by upstream, remove it from packaging.
+  * Build with hardening enabled and build-conflict with libcryptopp not to
+    mix up with libnss.
+  * Use symbol versioning (closes: #679686).
+  * Update debian/watch to GitHub tags.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sat, 07 Jul 2012 07:53:40 +0200
+
+ceph (0.47.2-1) unstable; urgency=low
+
+  * New upstream release.
+  * Use system leveldb (closes: #667907).
+  * Remove librgw1 , librgw-dev and librgw1-dbg and add rest-bench and
+    rest-bench-dbg packages.
+  * Backport leveldb build fixes from upstream git as
+    fix_leveldb_dep_for_system_library_case.patch and
+    fix_leveldb_includes_for_system_library_case.patch .
+  * Update packaging.
+  * Sync with Ubuntu: switch build-dependency from libcryptopp to libnss as
+    libcryptopp is not seeded.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 03 Jun 2012 13:37:52 +0200
+
+ceph (0.44.1-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Fri, 06 Apr 2012 01:10:15 +0200
+
+ceph (0.43-1) unstable; urgency=low
+
+  * New upstream release, now creates /var/run/ceph on each start
+    (closes: #660238).
+  * Update debian/copyright .
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 26 Feb 2012 04:07:02 +0100
+
+ceph (0.41-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 05 Feb 2012 10:07:38 +0100
+
+ceph (0.40-1) unstable; urgency=low
+
+  * New upstream release (closes: #652037).
+  * Adjust copyright to match upstream source changes.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sat, 14 Jan 2012 12:01:30 +0100
+
+ceph (0.38-1) unstable; urgency=low
+
+  * New upstream release (closes: #647764), missingok is now part of logrotate
+    directives (closes: #645651).
+  * Rename ceph-client-tools package to ceph-common , libceph-dev to
+    libcephfs-dev and libceph1{,-dbg} ones to libcephfs1{,-dbg} respectively.
+  * Update upstream VCS locations.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 27 Nov 2011 21:40:52 +0100
+
+ceph (0.35-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sat, 24 Sep 2011 16:51:57 +0200
+
+ceph (0.34-1) unstable; urgency=low
+
+  * New upstream release (closes: #638714).
+  * Make librbd-dev depends on librados-dev as it uses headers from the latter
+    (closes: #636845).
+  * Add new binary packages, gceph, gceph-dbg and obsync . The libcrush ones
+    removed.
+  * Change to quilt source format and tune packaging.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 28 Aug 2011 15:56:16 +0200
+
+ceph (0.27-1.1) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * Remove references to other libraries from dependency_libs field
+    (closes: #621208). 
+
+ -- Luk Claes <luk@debian.org>  Sat, 28 May 2011 22:28:48 +0200
+
+ceph (0.27-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Mon, 25 Apr 2011 10:09:05 +0200
+
+ceph (0.25.2-1) unstable; urgency=low
+
+  * New upstream release.
+  * Make Ceph cross buildable (closes: #618939), thanks to Hector Oron.
+  * Disable libatomic-ops on ARMv4t (armel) archs to prevent FTBFS
+    (closes: #615235), thanks go to Hector Oron again.
+  * Rename librados1{,-dbg,-dev} packages to librados2{,-dbg,-dev} ones;
+    conflict with and replace the former ones.
+  * Add librbd1 and librbd-dev packages.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sun, 27 Mar 2011 15:51:23 +0200
+
+ceph (0.24.3-2) unstable; urgency=low
+
+  * Make Ceph Linux only and build on all Linux archs (closes: #614890).
+  * Support parallel building via DEB_BUILD_OPTIONS .
+  * Add watch file, thanks to Clint Byrum (closes: #615021).
+  * Tune packaging.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Fri, 25 Feb 2011 15:17:26 +0100
+
+ceph (0.24.3-1) unstable; urgency=low
+
+  * New upstream bugfix release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sat, 19 Feb 2011 12:25:43 +0100
+
+ceph (0.24.2-1) unstable; urgency=low
+
+  * New upstream bugfix release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Sat, 29 Jan 2011 15:25:14 +0100
+
+ceph (0.24.1-1) unstable; urgency=low
+
+  * New upstream bugfix release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Tue, 11 Jan 2011 22:23:18 +0100
+
+ceph (0.24-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Laszlo Boszormenyi (GCS) <gcs@debian.hu>  Wed, 01 Dec 2010 09:26:25 -0800
+
+ceph (0.23.1-1) experimental; urgency=low
+
+  * Initial release (Closes: #506040)
+
+ -- Sage Weil <sage@newdream.net>  Sun, 21 Nov 2010 15:22:21 -0800
diff --git a/clean b/clean
new file mode 100644 (file)
index 0000000..8638ac9
--- /dev/null
+++ b/clean
@@ -0,0 +1,32 @@
+configure
+src/rocksdb/util/build_version.cc
+src/pybind/*.pyc
+src/test/pybind/*.pyc
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2005/gmock.sln
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2005/gmock.vcproj
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2005/gmock_config.vsprops
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2005/gmock_main.vcproj
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2005/gmock_test.vcproj
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2010/gmock.sln
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2010/gmock.vcxproj
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2010/gmock_config.props
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2010/gmock_main.vcxproj
+src/rapidjson/thirdparty/gtest/googlemock/msvc/2010/gmock_test.vcxproj
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest.cbproj
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest.groupproj
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_all.cc
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_link.cc
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_main.cbproj
+src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_unittest.cbproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest-md.sln
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest-md.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest.sln
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_main-md.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_main.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_prod_test-md.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_prod_test.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_unittest-md.vcproj
+src/rapidjson/thirdparty/gtest/googletest/msvc/gtest_unittest.vcproj
+debian/ceph-common.logrotate
+debian/radosgw.init
diff --git a/compat b/compat
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
+++ b/compat
@@ -0,0 +1 @@
+10
diff --git a/control b/control
new file mode 100644 (file)
index 0000000..ea89565
--- /dev/null
+++ b/control
@@ -0,0 +1,836 @@
+Source: ceph
+Section: admin
+Priority: optional
+Maintainer: Ceph Packaging Team <team+ceph@tracker.debian.org>
+Uploaders:
+ James Page <jamespage@debian.org>,
+ Gaudenz Steinlin <gaudenz@debian.org>,
+ Bernd Zeimetz <bzed@debian.org>,
+ Thomas Goirand <zigo@debian.org>,
+Build-Depends:
+ cmake,
+ cython3,
+ debhelper (>= 10~),
+ default-jdk,
+ dh-exec,
+ dh-python,
+ dpkg-dev (>= 1.16.1~),
+ gperf,
+ javahelper,
+ junit4,
+ libaio-dev,
+ libbabeltrace-ctf-dev,
+ libbabeltrace-dev,
+ libblkid-dev (>= 2.17),
+ libboost-atomic-dev (>= 1.67.0),
+ libboost-chrono-dev (>= 1.67.0),
+ libboost-context-dev (>= 1.67.0) [!s390x !mips64el !ia64 !m68k !ppc64 !riscv64 !sh4 !sparc64 !x32 !alpha],
+ libboost-coroutine-dev (>= 1.67.0) [!s390x !mips64el !ia64 !m68k !ppc64 !riscv64 !sh4 !sparc64 !x32 !alpha],
+ libboost-date-time-dev (>= 1.67.0),
+ libboost-iostreams-dev (>= 1.67.0),
+ libboost-program-options-dev (>= 1.67.0),
+ libboost-python-dev (>= 1.67.0),
+ libboost-random-dev (>= 1.67.0),
+ libboost-regex-dev (>= 1.67.0),
+ libboost-system-dev (>= 1.67.0),
+ libboost-thread-dev (>= 1.67.0),
+ libbz2-dev,
+ libcap-ng-dev,
+ libcunit1-dev,
+ libcurl4-gnutls-dev,
+ libedit-dev,
+ libexpat1-dev,
+ libfuse-dev,
+ libgoogle-perftools-dev [i386 amd64 powerpc armhf arm64 ppc64el],
+ libibverbs-dev,
+ libkeyutils-dev,
+ libldap2-dev,
+ libleveldb-dev,
+ liblz4-dev (>= 0.0~r131),
+ libncurses-dev,
+ libnl-3-dev,
+ libnl-genl-3-dev,
+ libnss3-dev,
+ liboath-dev,
+ librabbitmq-dev,
+ librdkafka-dev,
+ librdmacm-dev,
+ libsnappy-dev,
+ libssl-dev,
+ libtool,
+ libudev-dev,
+ libxml2-dev,
+ lsb-release,
+ pkg-config,
+ python3-cherrypy3,
+ python3-dev,
+ python3-pecan,
+ python3-setuptools,
+ python3-sphinx,
+ tox,
+ uuid-runtime,
+ valgrind [amd64 armhf i386 powerpc],
+ virtualenv,
+ xfslibs-dev,
+ yasm [amd64],
+ zlib1g-dev,
+Build-Conflicts:
+ libcrypto++-dev,
+Standards-Version: 4.2.1
+Vcs-Git: https://salsa.debian.org/ceph-team/ceph.git
+Vcs-Browser: https://salsa.debian.org/ceph-team/ceph
+Homepage: http://ceph.com/
+
+Package: ceph
+Architecture: linux-any
+Depends:
+ ceph-mgr (= ${binary:Version}),
+ ceph-mon (= ${binary:Version}),
+ ceph-osd (= ${binary:Version}),
+ ${misc:Depends},
+Suggests:
+ ceph-mds (= ${binary:Version}),
+Description: distributed storage and file system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+
+Package: ceph-base
+Architecture: linux-any
+Depends:
+ binutils,
+ ceph-common (= ${binary:Version}),
+ cryptsetup-bin | cryptsetup,
+ gdisk,
+ hdparm | sdparm,
+ parted,
+ uuid-runtime,
+ xfsprogs,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Pre-Depends:
+ ${misc:Pre-Depends},
+Breaks:
+ ceph (<< 10.2.2-0ubuntu2~),
+ ceph-common (<< 9.2.0-0~),
+ ceph-test (<< 12.2.8+dfsg1-1~),
+ python-ceph (<< 0.94.1-1~),
+Replaces:
+ ceph (<< 12.2.8+dfsg1-1~),
+ ceph-common (<< 9.2.0-0~),
+ ceph-test (<< 12.2.8+dfsg1-1~),
+ python-ceph (<< 0.94.1-1~),
+Recommends:
+ ceph-mds (= ${binary:Version}),
+ chrony | time-daemon | ntp,
+ librados2 (= ${binary:Version}),
+ librbd1 (= ${binary:Version}),
+Suggests:
+ btrfs-tools,
+ logrotate,
+Description: common ceph daemon libraries and management tools
+ Ceph is a distributed storage system designed to provide excellent
+ performance, reliability, and scalability.
+ .
+ This package contains the libraries and management tools that are common among
+ the Ceph server daemons (ceph-mon, ceph-mgr, ceph-osd, ceph-mds). These tools
+ are necessary for creating, running, and administering a Ceph storage cluster.
+
+Package: ceph-common
+Architecture: linux-any
+Depends:
+ librbd1 (= ${binary:Version}),
+ python3-cephfs (= ${binary:Version}),
+ python3-prettytable,
+ python3-rados (= ${binary:Version}),
+ python3-rbd (= ${binary:Version}),
+ python3-requests,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Conflicts:
+ ceph-client-tools,
+Breaks:
+ ceph (<< 9.2.0-0~),
+ ceph-base (<< 12.2.10+dfsg1-1~),
+ ceph-fs-common (<< 12.2.10+dfsg1-1~),
+ ceph-mds (<< 14.2.5-3~),
+ ceph-test (<< 9.2.0-0~),
+ librbd1 (<< 0.94.1-1~),
+ python-ceph (<< 0.94.1-1~),
+ radosgw (<< 12.0.3-0~),
+Replaces:
+ ceph (<< 9.2.0-0~),
+ ceph-client-tools,
+ ceph-fs-common (<< 12.2.8+dfsg1-1~),
+ ceph-mds (<< 14.2.5-3~),
+ ceph-test (<< 9.2.0-1~),
+ librbd1 (<< 0.94.1-1~),
+ python-ceph (<< 0.94.1-1~),
+ radosgw (<< 12.0.3-0~),
+Suggests:
+ ceph,
+ ceph-mds,
+Description: common utilities to mount and interact with a ceph storage cluster
+ Ceph is a distributed storage and file system designed to provide
+ excellent performance, reliability, and scalability.  This is a collection
+ of common tools that allow one to interact with and administer a Ceph cluster.
+
+Package: ceph-fuse
+Architecture: amd64
+Depends:
+ python3,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Recommends:
+ fuse,
+Description: FUSE-based client for the Ceph distributed file system
+ Ceph is a distributed network file system designed to provide
+ excellent performance, reliability, and scalability.  This is a
+ FUSE-based client that allows one to mount a Ceph file system without
+ root privileges.
+ .
+ Because the FUSE-based client has certain inherent performance
+ limitations, it is recommended that the native Linux kernel client
+ be used if possible.  If it is not practical to load a kernel module
+ (insufficient privileges, older kernel, etc.), then the FUSE client will
+ do.
+
+Package: ceph-mds
+Architecture: linux-any
+Depends:
+ ceph,
+ ${misc:Depends},
+ ${shlibs:Depends},
+Recommends:
+ ceph-common,
+ ceph-fuse,
+ libcephfs2,
+Breaks:
+ ceph (<< 0.67.3-1),
+Replaces:
+ ceph (<< 0.67.3-1),
+Description: metadata server for the ceph distributed file system
+ Ceph is a distributed storage and network file system designed to
+ provide excellent performance, reliability, and scalability.
+ .
+ This package contains the metadata server daemon, which is used to
+ create a distributed file system on top of the ceph storage cluster.
+
+Package: ceph-mgr
+Architecture: linux-any
+Depends:
+ ceph-base (= ${binary:Version}),
+ python3-bcrypt,
+ python3-cherrypy3,
+ python3-jwt,
+ python3-openssl,
+ python3-pecan,
+ python3-werkzeug,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Replaces:
+ ceph (<< 0.93-417),
+Breaks:
+ ceph (<< 0.93-417),
+Suggests:
+ ceph-mgr-dashboard,
+ ceph-mgr-diskprediction-cloud,
+ ceph-mgr-diskprediction-local,
+ ceph-mgr-rook,
+ ceph-mgr-ssh,
+Description: manager for the ceph distributed file system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the manager daemon, which is used to expose high
+ level management and monitoring functionality.
+
+Package: ceph-mgr-dashboard
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ python3-bcrypt,
+ python3-cherrypy3,
+ python3-distutils,
+ python3-jwt,
+ python3-openssl,
+ python3-routes,
+ python3-werkzeug,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: dashboard plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package provides a ceph-mgr plugin, providing a web-based
+ application to monitor and manage many aspects of a Ceph cluster and
+ related components.
+ .
+ See the Dashboard documentation at http://docs.ceph.com/ for details
+ and a detailed feature overview.
+
+Package: ceph-mgr-diskprediction-cloud
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: diskprediction-cloud plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the diskprediction_cloud plugin for the ceph-mgr
+ daemon, which helps predict disk failures.
+
+Package: ceph-mgr-diskprediction-local
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ python3-numpy,
+ python3-scipy,
+ python3-sklearn,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: diskprediction-local plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the diskprediction_local plugin for the ceph-mgr
+ daemon, which helps predict disk failures.
+
+Package: ceph-mgr-k8sevents
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ python3-kubernetes,
+ ${misc:Depends},
+ ${python:Depends},
+Description: kubernetes events plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the k8sevents plugin, to allow ceph-mgr to send
+ ceph related events to the kubernetes events API, and track all events
+ that occur within the rook-ceph namespace.
+
+Package: ceph-mgr-rook
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ python3-six,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: rook plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the rook plugin for ceph-mgr's orchestration
+ functionality, to allow ceph-mgr to install and configure ceph using
+ Rook.
+
+Package: ceph-mgr-ssh
+Architecture: all
+Depends:
+ ceph-mgr (>= ${binary:Version}),
+ python3-six,
+ ${misc:Depends},
+ ${python3:Depends},
+Description: ssh orchestrator plugin for ceph-mgr
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the SSH plugin for ceph-mgr's orchestration
+ functionality, to allow ceph-mgr to perform orchestration functions
+ over a standard SSH connection.
+
+Package: ceph-mon
+Architecture: linux-any
+Depends:
+ ceph-base (= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Replaces:
+ ceph (<< 10.2.2-0ubuntu2~),
+Breaks:
+ ceph (<< 10.2.2-0ubuntu2~),
+Description: monitor server for the ceph storage system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the cluster monitor daemon for the Ceph storage
+ system. One or more instances of ceph-mon form a Paxos part-time parliament
+ cluster that provides extremely reliable and durable storage of cluster
+ membership, configuration, and state.
+
+Package: ceph-osd
+Architecture: linux-any
+Depends:
+ ceph-base (= ${binary:Version}),
+ lvm2,
+ smartmontools (>= 7.0),
+ sudo,
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Suggests:
+ nvme-cli,
+Pre-Depends:
+ ceph-common (= ${binary:Version}),
+Replaces:
+ ceph (<< 10.2.2-0ubuntu2~),
+ ceph-test (<< 12.2.8+dfsg1-1~),
+Breaks:
+ ceph (<< 10.2.2-0ubuntu2~),
+ ceph-test (<< 12.2.8+dfsg1-1~),
+Description: OSD server for the ceph storage system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains the Object Storage Daemon for the Ceph storage system.
+ It is responsible for storing objects on a local file system
+ and providing access to them over the network.
+
+Package: ceph-resource-agents
+Architecture: all
+Priority: optional
+Recommends:
+ pacemaker,
+Depends:
+ ceph (>= ${binary:Version}),
+ resource-agents,
+ ${misc:Depends},
+Description: OCF-compliant resource agents for Ceph
+ Ceph is a distributed storage and network file system designed to provide
+ excellent performance, reliability, and scalability.
+ .
+ This package contains the resource agents (RAs) which integrate
+ Ceph with OCF-compliant cluster resource managers,
+ such as Pacemaker.
+
+Package: cephfs-shell
+Architecture: all
+Depends:
+ ${misc:Depends},
+ ${python3:Depends},
+Description: interactive shell for the Ceph distributed file system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.  This is an interactive tool that
+ allows accessing a Ceph file system without mounting it by providing
+ a nice pseudo-shell which works like an FTP client.
+ .
+ This package contains a CLI for interacting with the CephFS.
+
+Package: libcephfs-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ libcephfs2 (= ${binary:Version}),
+ ${misc:Depends},
+Conflicts:
+ libceph-dev,
+ libceph1-dev,
+ libcephfs2-dev,
+Replaces:
+ libceph-dev,
+ libceph1-dev,
+ libcephfs2-dev,
+Description: Ceph distributed file system client library (development files)
+ Ceph is a distributed network file system designed to provide
+ excellent performance, reliability, and scalability.  This is a
+ shared library allowing applications to access a Ceph distributed
+ file system via a POSIX-like interface.
+ .
+ This package contains development files needed for building applications that
+ link against libcephfs2.
+
+Package: libcephfs-java
+Architecture: all
+Section: java
+Depends:
+ libcephfs-jni (>= ${binary:Version}),
+ ${java:Depends},
+ ${misc:Depends},
+Description: Java library for the Ceph File System
+ Ceph is a distributed storage system designed to provide excellent
+ performance, reliability, and scalability.
+ .
+ This package contains the Java library for interacting with the Ceph
+ File System.
+
+Package: libcephfs-jni
+Architecture: linux-any
+Section: libs
+Depends:
+ libcephfs2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: Java Native Interface library for CephFS Java bindings
+ Ceph is a distributed storage system designed to provide excellent
+ performance, reliability, and scalability.
+ .
+ This package contains the Java Native Interface library for interacting
+ with the Ceph File System.
+
+Package: libcephfs2
+Architecture: linux-any
+Section: libs
+Conflicts:
+ libceph,
+ libceph1,
+ libcephfs,
+Replaces:
+ libceph,
+ libceph1,
+ libcephfs,
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Pre-Depends:
+ ${misc:Pre-Depends},
+Description: Ceph distributed file system client library
+ Ceph is a distributed network file system designed to provide
+ excellent performance, reliability, and scalability.  This is a
+ shared library allowing applications to access a Ceph distributed
+ file system via a POSIX-like interface.
+
+Package: librados-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Conflicts:
+ librados1-dev,
+ librados2-dev,
+Replaces:
+ librados1-dev,
+ librados2-dev,
+Description: RADOS distributed object store client library (development files)
+ RADOS is a reliable, autonomic distributed object storage cluster
+ developed as part of the Ceph distributed storage system.  This is a
+ shared library allowing applications to access the distributed object
+ store using a simple file-like interface.
+ .
+ This package contains development files needed for building applications that
+ link against librados2.
+
+Package: librados2
+Architecture: linux-any
+Section: libs
+Conflicts:
+ librados,
+ librados1,
+Replaces:
+ librados,
+ librados1,
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Pre-Depends:
+ ${misc:Pre-Depends},
+Description: RADOS distributed object store client library
+ RADOS is a reliable, autonomic distributed object storage cluster
+ developed as part of the Ceph distributed storage system.  This is a
+ shared library allowing applications to access the distributed object
+ store using a simple file-like interface.
+
+Package: libradospp-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ librados-dev (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: RADOS distributed object store client C++ library (development files)
+ RADOS is a reliable, autonomic distributed object storage cluster
+ developed as part of the Ceph distributed storage system.  This is a
+ shared library allowing applications to access the distributed object
+ store using a simple file-like interface.
+ .
+ This package contains development files needed for building C++ applications that
+ link against librados.
+
+Package: libradosstriper-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ libradosstriper1 (= ${binary:Version}),
+ ${misc:Depends},
+Description: RADOS striping interface (development files)
+ libradosstriper is a striping interface built on top of the rados
+ library, allowing to stripe bigger objects onto several standard
+ rados objects using an interface very similar to the rados one.
+ .
+ This package contains development files needed for building applications that
+ link against libradosstriper.
+
+Package: libradosstriper1
+Architecture: linux-any
+Section: libs
+Depends:
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: RADOS striping interface
+ Striping interface built on top of the rados library, allowing
+ to stripe bigger objects onto several standard rados objects using
+ an interface very similar to the rados one.
+
+Package: librbd-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ librados-dev,
+ librbd1 (= ${binary:Version}),
+ ${misc:Depends},
+Conflicts:
+ librbd1-dev,
+Replaces:
+ librbd1-dev,
+Description: RADOS block device client library (development files)
+ RBD is a block device striped across multiple distributed objects
+ in RADOS, a reliable, autonomic distributed object storage cluster
+ developed as part of the Ceph distributed storage system.  This is a
+ shared library allowing applications to manage these block devices.
+ .
+ This package contains development files needed for building applications that
+ link against librbd1.
+
+Package: librbd1
+Architecture: linux-any
+Section: libs
+Depends:
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Pre-Depends:
+ ${misc:Pre-Depends},
+Description: RADOS block device client library
+ RBD is a block device striped across multiple distributed objects
+ in RADOS, a reliable, autonomic distributed object storage cluster
+ developed as part of the Ceph distributed storage system.  This is a
+ shared library allowing applications to manage these block devices.
+
+Package: librgw-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ librados-dev (= ${binary:Version}),
+ librgw2 (= ${binary:Version}),
+ ${misc:Depends},
+Description: RADOS client library (development files)
+ RADOS is a distributed object store used by the Ceph distributed
+ storage system.  This package provides a REST gateway to the
+ object store that aims to implement a superset of Amazon's S3
+ service.
+ .
+ This package contains development files needed for building applications
+ that link against librgw2.
+
+Package: librgw2
+Architecture: linux-any
+Section: libs
+Depends:
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: RADOS Gateway client library
+ RADOS is a distributed object store used by the Ceph distributed
+ storage system.  This package provides a REST gateway to the
+ object store that aims to implement a superset of Amazon's S3
+ service.
+ .
+ This package contains the library interface and headers only.
+
+Package: python3-ceph
+Architecture: all
+Section: python
+Depends:
+ python3-cephfs (<< ${source:Version}.1~),
+ python3-cephfs (>= ${source:Version}),
+ python3-rados (<< ${source:Version}.1~),
+ python3-rados (>= ${source:Version}),
+ python3-rbd (<< ${source:Version}.1~),
+ python3-rbd (>= ${source:Version}),
+ python3-rgw (<< ${source:Version}.1~),
+ python3-rgw (>= ${source:Version}),
+ ${misc:Depends},
+Description: Meta-package for all Python 3.x modules for the Ceph libraries
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package is a metapackage for all Ceph Python 3.x bindings.
+
+Package: python3-ceph-argparse
+Architecture: linux-any
+Section: python
+Depends:
+ ${misc:Depends},
+ ${python3:Depends},
+Breaks:
+ ceph-common (<< 14.2.1-0~),
+Replaces:
+ ceph-common (<< 14.2.1-0~),
+Description: Python 3 utility libraries for Ceph CLI
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains types and routines for Python 3 used by the
+ Ceph CLI as well as the RESTful interface.
+
+Package: python3-cephfs
+Architecture: linux-any
+Section: python
+Depends:
+ libcephfs2 (= ${binary:Version}),
+ python3-ceph-argparse (= ${binary:Version}),
+ python3-rados (= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: Python 3 libraries for the Ceph libcephfs library
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains Python 3 libraries for interacting with Ceph's
+ CephFS file system client library.
+
+Package: python3-rados
+Architecture: linux-any
+Section: python
+Depends:
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: Python 3 libraries for the Ceph librados library
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains Python 3 libraries for interacting with Ceph's
+ RADOS object storage.
+
+Package: python3-rbd
+Architecture: linux-any
+Section: python
+Depends:
+ librbd1 (>= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: Python 3 libraries for the Ceph librbd library
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains Python 3 libraries for interacting with Ceph's
+ RBD block device library.
+
+Package: python3-rgw
+Architecture: linux-any
+Section: python
+Depends:
+ librgw2 (>= ${binary:Version}),
+ python3-rados (= ${binary:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+Description: Python 3 libraries for the Ceph librgw library
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.
+ .
+ This package contains Python 3 libraries for interacting with Ceph's
+ RGW library.
+
+Package: rados-objclass-dev
+Architecture: linux-any
+Section: libdevel
+Depends:
+ librados-dev (= ${binary:Version}),
+ ${misc:Depends},
+Description: RADOS object class development kit.
+ .
+ This package contains development files needed for building RADOS object class plugins.
+
+Package: radosgw
+Architecture: linux-any
+Depends:
+ ceph-common (= ${binary:Version}),
+ librgw2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Suggests:
+ logrotate,
+Description: REST gateway for RADOS distributed object store
+ RADOS is a distributed object store used by the Ceph distributed
+ storage system.  This package provides a REST gateway to the
+ object store that aims to implement a superset of Amazon's S3
+ service as well as the OpenStack Object Storage ("Swift") API.
+ .
+ This package contains the proxy daemon and related tools only.
+
+Package: rbd-fuse
+Architecture: linux-any
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Recommends:
+ fuse,
+Description: FUSE-based rbd client for the Ceph distributed file system
+ Ceph is a distributed network file system designed to provide
+ excellent performance, reliability, and scalability.  This is a
+ FUSE-based client that allows one to map Ceph rbd images as files.
+
+Package: rbd-mirror
+Architecture: linux-any
+Depends:
+ ceph-common (= ${binary:Version}),
+ librados2 (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: Ceph daemon for mirroring RBD images
+ Ceph is a distributed storage system designed to provide excellent
+ performance, reliability, and scalability.
+ .
+ This package provides a daemon for mirroring RBD images between
+ Ceph clusters, streaming changes asynchronously.
+
+Package: rbd-nbd
+Architecture: linux-any
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+Description: NBD-based rbd client for the Ceph distributed file system
+ Ceph is a massively scalable, open-source, distributed
+ storage system that runs on commodity hardware and delivers object,
+ block and file system storage.  This is a
+ NBD-based client that allows one to map Ceph rbd images as local
+ block device.
+ .
+ NBD base client that allows one to map Ceph rbd images as local
+ block device.
diff --git a/copyright b/copyright
new file mode 100644 (file)
index 0000000..390a68a
--- /dev/null
+++ b/copyright
@@ -0,0 +1,977 @@
+Format-Specification: http://anonscm.debian.org/viewvc/dep/web/deps/dep5/copyright-format.xml?revision=279&view=markup
+Name: ceph
+Maintainer: Sage Weil <sage@newdream.net>
+Source: http://ceph.com/
+
+Files: *
+Copyright: 2004-2014 Sage Weil <sage@newdream.net>
+           2004-2014 Inktank <info@inktank.com>
+                     Inktank, Inc
+                     Inktank Storage, Inc.
+           2012-2014 Red Hat <contact@redhat.com>
+           2013-2014 Cloudwatt <libre.licensing@cloudwatt.com>
+           2013      CohortFS, LLC
+           2004-2011 Dreamhost
+           2013      eNovance SAS <licensing@enovance.com>
+           2014      Adam Crume <adamcrume@gmail.com>
+           2012      Florian Haas, hastexo
+           2010      Greg Farnum <gregf@hq.newdream.net>
+           2014      John Spray <john.spray@inktank.com
+           2004-2012 New Dream Network
+           2014      Sebastien Ponce <sebastien.ponce@cern.ch>
+           2011      Stanislav Sedov <stas@FreeBSD.org>
+           2013-2014 UnitedStack <haomai@unitedstack.com>
+           2011      Wido den Hollander <wido@widodh.nl>
+License: LGPL2.1 (see COPYING-LGPL2.1)
+
+Files: cmake/modules/FindLTTngUST.cmake
+Copyright:
+    Copyright 2016 Kitware, Inc.
+    Copyright 2016 Philippe Proulx <pproulx@efficios.com>
+License: BSD 3-clause
+
+Files: doc/*
+Copyright: (c) 2010-2012 New Dream Network and contributors
+License: Creative Commons Attribution Share Alike 3.0 (CC-BY-SA-3.0)
+
+Files: bin/git-archive-all.sh
+License: GPL3
+
+Files: src/mount/canonicalize.c
+Copyright: Copyright (C) 1993 Rick Sladkey <jrs@world.std.com>
+License: LGPL2 or later (see COPYING-GPL2)
+
+
+Files: src/os/btrfs_ioctl.h
+Copyright: Copyright (C) 2007 Oracle.  All rights reserved.
+License: GPL2 (see COPYING-GPL2)
+
+Files: src/include/ceph_hash.cc
+Copyright: None
+License: Public domain
+
+Files: src/common/bloom_filter.hpp
+Copyright: Copyright (C) 2000 Arash Partow <arash@partow.net>
+License: Boost Software License, Version 1.0
+
+Files: src/common/crc32c_intel*:
+Copyright:
+    Copyright 2012-2013 Intel Corporation All Rights Reserved.
+License: BSD 3-clause
+
+Files: src/common/sctp_crc32.c:
+Copyright:
+    Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+    Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved
+License:
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  a) Redistributions of source code must retain the above copyright notice,
+    this list of conditions and the following disclaimer.
+
+  b) 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.
+
+  c) Neither the name of Cisco Systems, 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.
+
+Files: src/json_spirit
+Copyright:
+       Copyright John W. Wilkinson 2007 - 2011
+License:
+  The MIT License
+
+  Copyright (c) 2007 - 2010 John W. Wilkinson
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+  OTHER DEALINGS IN THE SOFTWARE.
+
+Files: src/test/common/Throttle.cc src/test/filestore/chain_xattr.cc
+Copyright: Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
+License: LGPL2.1 or later
+
+
+
+Files: src/osd/ErasureCodePluginJerasure/*.{c,h}
+Copyright: Copyright (c) 2011, James S. Plank <plank@cs.utk.edu>
+License:
+  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 the University of Tennessee 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
+  HOLDER 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.
+
+
+Files: qa/workunits/erasure-code/jquery.js
+Copyright: 2012 jQuery Foundation and other contributors
+License: MIT
+
+Files: qa/workunits/erasure-code/jquery.{flot.categories,flot}.js
+Copyright: 2007-2014 IOLA and Ole Laursen.
+License: MIT
+
+Files: src/include/timegm.h
+Copyright: Howard Hinnant
+           2010-2011 Vicente J. Botet Escriba
+License: Boost Software License, Version 1.0
+
+Files: src/pybind/mgr/diskprediction_local/models/*
+Copyright: None
+License: Public domain
+
+Files: src/ceph-volume/plugin/zfs/*
+Copyright: 2018, Willem Jan Withagen
+License: BSD 3-clause
+
+
+Files: src/test/perf_local.cc
+Copyright:
+  (c) 2011-2014 Stanford University
+  (c) 2011 Facebook
+License:
+  The MIT License
+
+
+Comment: -----------------------------------------------
+ Content above is taken from upstream's COPYING file.
+ Unfortunately it is incomplete. Debian/Ubuntu packaging
+ findings/additions are below.
+ -------------------------------------------------------
+
+
+Files: src/erasure-code/jerasure/ErasureCode*
+       src/erasure-code/ErasureCode*
+       src/erasure-code/isa/*
+       src/include/str_map.h
+       src/test/common/test_str_map.cc
+       src/test/erasure-code/*
+       src/test/rgw/test_rgw_manifest.cc
+Copyright: 2014      CERN/Switzerland
+           2013-2014 Cloudwatt <libre.licensing@cloudwatt.com>
+           2014      Red Hat <contact@redhat.com>
+           2013      eNovance SAS <licensing@enovance.com>
+License: LGPL-2.1+
+
+
+Files: src/erasure-code/isa/isa-l/erasure_code/*
+Copyright: 2011-2014 Intel Corporation
+License: BSD-3-clause
+
+Files: src/rocksdb/*
+Copyright: 2004-2013 Facebook, Inc.
+           2011      The LevelDB Authors
+           2009      Google Inc.
+License: BSD-3-clause
+Comment:
+ Additional Grant of Patent Rights
+ .
+ “Software” means the rocksdb software distributed by Facebook, Inc.
+ .
+ Facebook hereby grants you a perpetual, worldwide, royalty-free,
+ non-exclusive, irrevocable (subject to the termination provision below)
+ license under any rights in any patent claims owned by Facebook, to make,
+ have made, use, sell, offer to sell, import, and otherwise transfer the
+ Software. For avoidance of doubt, no license is granted under Facebook’s
+ rights in any patent claims that are infringed by (i) modifications to the
+ Software made by you or a third party, or (ii) the Software in combination
+ with any software or other technology provided by you or a third party.
+ .
+ The license granted hereunder will terminate, automatically and without
+ notice, for anyone that makes any claim (including by filing any lawsuit,
+ assertion or other action) alleging (a) direct, indirect, or contributory
+ infringement or inducement to infringe any patent: (i) by Facebook or any
+ of its subsidiaries or affiliates, whether or not such claim is related
+ to the Software, (ii) by any party if such claim arises in whole or in
+ part from any software, product or service of Facebook or any of its
+ subsidiaries or affiliates, whether or not such claim is related to the
+ Software, or (iii) by any party relating to the Software; or (b) that
+ any right in any patent claim of Facebook is invalid or unenforceable.
+
+
+Files: src/rocksdb/util/xxhash.*
+Copyright: 2012-2014, Yann Collet.
+License: BSD-2-clause
+ 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.
+ .
+ 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.
+
+
+
+Files: src/mount/canonicalize.c
+       src/test/common/test_config.cc
+       src/test/crush/TestCrushWrapper.cc
+       src/test/common/Throttle.cc
+       src/test/objectstore/chain_xattr.cc
+       src/test/mon/mon-test-helpers.sh
+       src/test/objectstore/chain_xattr.cc
+       src/test/osd/osd-test-helpers.sh
+       src/ceph-disk
+       src/stop.sh
+Copyright: 1993      Rick Sladkey <jrs@world.std.com>
+           2013      Inktank <info@inktank.com>
+           2013-2014 Cloudwatt <libre.licensing@cloudwatt.com>
+License: LGPL-2+
+
+Files: src/os/btrfs_ioctl.h
+       src/test/mon/PGMap.cc
+Copyright: 2007 Oracle.  All rights reserved.
+           2014 Inktank <info@inktank.com>
+License: GPL-2
+
+Files: src/common/ceph_hash.cc
+Copyright: 1995-1997 Robert J. Jenkins Jr.
+License: public-domain
+  This file uses Robert Jenkin's hash function as detailed at:
+  .
+    http://burtleburtle.net/bob/hash/evahash.html
+  .
+  This is in the public domain.
+
+Files: src/common/bloom_filter.hpp
+Copyright: 2000 Arash Partow
+License: Boost-Software-License-1.0
+ Permission is hereby granted, free of charge, to any person or organization
+ obtaining a copy of the software and accompanying documentation covered by
+ this license (the "Software") to use, reproduce, display, distribute,
+ execute, and transmit the Software, and to prepare derivative works of the
+ Software, and to permit third-parties to whom the Software is furnished to
+ do so, all subject to the following:
+ .
+   The copyright notices in the Software and this entire statement,
+   including the above license grant, this restriction and the following
+   disclaimer, must be included in all copies of the Software, in whole or
+   in part, and all derivative works of the Software, unless such copies
+   or derivative works are solely in the form of machine-executable object
+   code generated by a source language processor.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+Files: src/common/crc32c_intel*
+Copyright: 2012-2013 Intel Corporation All Rights Reserved.
+License: BSD-3-clause
+
+Files: src/common/sctp_crc32.c
+Copyright: 2001-2007, by Cisco Systems, Inc. All rights reserved,
+           2004-2006 Intel Corporation - All Rights Reserved
+License: BSD-3-clause
+
+Files: src/erasure-code/jerasure/gf-complete/*/*
+Copyright: 2013 James S. Plank
+                Ethan L. Miller
+                Kevin M. Greenan
+                Benjamin A. Arnold
+                John A. Burnum
+                Adam W. Disney
+                Allen C. McBride
+License: BSD-3-clause
+Comment:
+ https://bitbucket.org/jimplank/gf-complete
+
+Files: src/erasure-code/jerasure/jerasure/*/*
+Copyright: 2011-2013 James S. Plank <plank@cs.utk.edu>
+           2013      Kevin Greenan
+License: BSD-3-clause
+
+Files: src/gtest/*
+Copyright: 2008, Google Inc.
+License: BSD-3-clause
+
+Files: src/civetweb/*
+Copyright: 2004-2013 Sergey Lyubka
+           2013-2014 the Civetweb developers
+License: Expat
+
+Files: src/json_spirit/*
+Copyright: 2007-2011, John W. Wilkinson
+License: Expat
+
+Files: src/java/native/ScopedLocalRef.h
+       src/java/native/JniConstants.*
+Copyright: 2010 The Android Open Source Project
+License: Apache-2.0
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+      http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ .
+ The complete text of the Apache License, Version 2.0
+ can be found in "/usr/share/common-licenses/Apache-2.0".
+
+Files: src/libs3/*
+Copyright: 2008 Bryan Ischo <bryan@ischo.com>
+License: GPL-3/OpenSSL
+ libs3 is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, version 3 of the License.
+ .
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this library and its programs with the
+ OpenSSL library, and distribute linked combinations including the two.
+ .
+ libs3 is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ The complete text of the GNU General Public License version 3
+ can be found in "/usr/share/common-licenses/GPL-3' file.
+
+
+Files: src/mount/mtab.c
+Copyright: util-linux-ng AUTHORS
+License: GPL-2+
+Comment:
+ "mount/fstab.c" from line 559:
+ https://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/mount-deprecated/fstab.c?h=v2.22#n559
+ https://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/README.licensing
+
+Files: src/test/librbd/fsx.c
+Copyright: 1991, NeXT Computer, Inc.
+License: APSL-2.0
+ The contents of this file constitute Original Code as defined in and
+ are subject to the Apple Public Source License Version 2.0 (the
+ "License"). You may not use this file except in compliance with the
+ License. Please obtain a copy of the License at
+ http://www.opensource.apple.com/apsl/ and read it before using this file.
+ .
+ This Original Code and all software distributed under the License are
+ distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
+ License for the specific language governing rights and limitations
+ under the License.
+Comment:
+ http://codemonkey.org.uk/projects/fsx/
+ http://codemonkey.org.uk/projects/fsx/fsx-macosforge/fsx.c
+
+Files: man/*
+       debian/man/*
+Copyright: 2010-2014, Inktank Storage, Inc. and contributors.
+License: CC-BY-SA-3.0
+
+
+Files: debian/missing-sources/bootstrap.js
+Copyright: 2011-2015 Twitter, Inc
+License: MIT
+
+Files: debian/missing-sources/two.js
+Copyright: 2012 - 2017 jonobr1 / http://jonobr1.com
+License: MIT
+
+Files: debian/*
+Copyright: 2010      Sage Weil <sage@newdream.net>
+           2010      Canonical, Ltd.
+           2011-2013 László Böszörményi (GCS) <gcs@debian.org>
+           2013-2014 James Page <james.page@ubuntu.com>
+           2014      Dmitry Smirnov <onlyjob@debian.org>
+           2019      Bernd Zeimetz <bzed@debian.org>
+License: LGPL-2.1
+
+License: GPL-2
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ version 2 can be found in `/usr/share/common-licenses/GPL-2' file.
+
+License: GPL-2+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ version 2 can be found in `/usr/share/common-licenses/GPL-2'.
+
+License: LGPL-2.1
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2.1 as published by the Free Software Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License can be found in `/usr/share/common-licenses/LGPL-2.1'.
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License can be found in `/usr/share/common-licenses/LGPL-2.1'.
+
+License: LGPL-2+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2 (or later) as published by the Free Software Foundation.
+ .
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License 2 can be found in `/usr/share/common-licenses/LGPL-2'.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+Comment:
+ This license also known as "MIT" however FSF consider "MIT" labelling
+ ambiguous and copyright-format specification recommend to label such license
+ as "Expat".
+
+License: CC-BY-SA-3.0
+ Creative Commons Attribution-ShareAlike 3.0 Unported
+ ․
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
+ ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE
+ INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ ITS USE.
+ ․
+ License
+ ․
+ THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+ COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+ COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+ AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+ ․
+ BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
+ TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
+ BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
+ CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+ CONDITIONS.
+ ․
+ 1. Definitions
+ ․
+ a. "Adaptation" means a work based upon the Work, or upon the Work and
+ other pre-existing works, such as a translation, adaptation, derivative
+ work, arrangement of music or other alterations of a literary or
+ artistic work, or phonogram or performance and includes cinematographic
+ adaptations or any other form in which the Work may be recast,
+ transformed, or adapted including in any form recognizably derived from
+ the original, except that a work that constitutes a Collection will not
+ be considered an Adaptation for the purpose of this License. For the
+ avoidance of doubt, where the Work is a musical work, performance or
+ phonogram, the synchronization of the Work in timed-relation with a
+ moving image ("synching") will be considered an Adaptation for the
+ purpose of this License.
+ ․
+ b. "Collection" means a collection of literary or artistic works, such
+ as encyclopedias and anthologies, or performances, phonograms or
+ broadcasts, or other works or subject matter other than works listed in
+ Section 1(f) below, which, by reason of the selection and arrangement of
+ their contents, constitute intellectual creations, in which the Work is
+ included in its entirety in unmodified form along with one or more other
+ contributions, each constituting separate and independent works in
+ themselves, which together are assembled into a collective whole. A work
+ that constitutes a Collection will not be considered an Adaptation (as
+ defined below) for the purposes of this License.
+ ․
+ c. "Creative Commons Compatible License" means a license that is listed
+ at http://creativecommons.org/compatiblelicenses that has been approved
+ by Creative Commons as being essentially equivalent to this License,
+ including, at a minimum, because that license: (i) contains terms that
+ have the same purpose, meaning and effect as the License Elements of
+ this License; and, (ii) explicitly permits the relicensing of
+ adaptations of works made available under that license under this
+ License or a Creative Commons jurisdiction license with the same License
+ Elements as this License.
+ ․
+ d. "Distribute" means to make available to the public the original and
+ copies of the Work or Adaptation, as appropriate, through sale or other
+ transfer of ownership.
+ ․
+ e. "License Elements" means the following high-level license attributes
+ as selected by Licensor and indicated in the title of this License:
+ Attribution, ShareAlike.
+ ․
+ f. "Licensor" means the individual, individuals, entity or entities that
+ offer(s) the Work under the terms of this License.
+ ․
+ g. "Original Author" means, in the case of a literary or artistic work,
+ the individual, individuals, entity or entities who created the Work or
+ if no individual or entity can be identified, the publisher; and in
+ addition (i) in the case of a performance the actors, singers,
+ musicians, dancers, and other persons who act, sing, deliver, declaim,
+ play in, interpret or otherwise perform literary or artistic works or
+ expressions of folklore; (ii) in the case of a phonogram the producer
+ being the person or legal entity who first fixes the sounds of a
+ performance or other sounds; and, (iii) in the case of broadcasts, the
+ organization that transmits the broadcast.
+ ․
+ h. "Work" means the literary and/or artistic work offered under the
+ terms of this License including without limitation any production in the
+ literary, scientific and artistic domain, whatever may be the mode or
+ form of its expression including digital form, such as a book, pamphlet
+ and other writing; a lecture, address, sermon or other work of the same
+ nature; a dramatic or dramatico-musical work; a choreographic work or
+ entertainment in dumb show; a musical composition with or without words;
+ a cinematographic work to which are assimilated works expressed by a
+ process analogous to cinematography; a work of drawing, painting,
+ architecture, sculpture, engraving or lithography; a photographic work
+ to which are assimilated works expressed by a process analogous to
+ photography; a work of applied art; an illustration, map, plan, sketch
+ or three-dimensional work relative to geography, topography,
+ architecture or science; a performance; a broadcast; a phonogram; a
+ compilation of data to the extent it is protected as a copyrightable
+ work; or a work performed by a variety or circus performer to the extent
+ it is not otherwise considered a literary or artistic work.
+ ․
+ i. "You" means an individual or entity exercising rights under this
+ License who has not previously violated the terms of this License with
+ respect to the Work, or who has received express permission from the
+ Licensor to exercise rights under this License despite a previous
+ violation.
+ ․
+ j. "Publicly Perform" means to perform public recitations of the Work
+ and to communicate to the public those public recitations, by any means
+ or process, including by wire or wireless means or public digital
+ performances; to make available to the public Works in such a way that
+ members of the public may access these Works from a place and at a place
+ individually chosen by them; to perform the Work to the public by any
+ means or process and the communication to the public of the performances
+ of the Work, including by public digital performance; to broadcast and
+ rebroadcast the Work by any means including signs, sounds or images.
+ ․
+ k. "Reproduce" means to make copies of the Work by any means including
+ without limitation by sound or visual recordings and the right of
+ fixation and reproducing fixations of the Work, including storage of a
+ protected performance or phonogram in digital form or other electronic
+ medium.
+ ․
+ 2. Fair Dealing Rights. Nothing in this License is intended to reduce,
+ limit, or restrict any uses free from copyright or rights arising from
+ limitations or exceptions that are provided for in connection with the
+ copyright protection under copyright law or other applicable laws.
+ ․
+ 3. License Grant. Subject to the terms and conditions of this License,
+ Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+ perpetual (for the duration of the applicable copyright) license to
+ exercise the rights in the Work as stated below:
+ ․
+ a. to Reproduce the Work, to incorporate the Work into one or more
+ Collections, and to Reproduce the Work as incorporated in the
+ Collections;
+ ․
+ b. to create and Reproduce Adaptations provided that any such
+ Adaptation, including any translation in any medium, takes reasonable
+ steps to clearly label, demarcate or otherwise identify that changes
+ were made to the original Work. For example, a translation could be
+ marked "The original work was translated from English to Spanish," or a
+ modification could indicate "The original work has been modified.";
+ ․
+ c. to Distribute and Publicly Perform the Work including as incorporated
+ in Collections; and,
+ ․
+ d. to Distribute and Publicly Perform Adaptations.
+ ․
+ e. For the avoidance of doubt:
+ ․
+ i. Non-waivable Compulsory License Schemes. In those jurisdictions in
+ which the right to collect royalties through any statutory or compulsory
+ licensing scheme cannot be waived, the Licensor reserves the exclusive
+ right to collect such royalties for any exercise by You of the rights
+ granted under this License;
+ ․
+ ii. Waivable Compulsory License Schemes. In those jurisdictions in which
+ the right to collect royalties through any statutory or compulsory
+ licensing scheme can be waived, the Licensor waives the exclusive right
+ to collect such royalties for any exercise by You of the rights granted
+ under this License; and,
+ ․
+ iii. Voluntary License Schemes. The Licensor waives the right to collect
+ royalties, whether individually or, in the event that the Licensor is a
+ member of a collecting society that administers voluntary licensing
+ schemes, via that society, from any exercise by You of the rights
+ granted under this License.
+ ․
+ The above rights may be exercised in all media and formats whether now
+ known or hereafter devised. The above rights include the right to make
+ such modifications as are technically necessary to exercise the rights
+ in other media and formats. Subject to Section 8(f), all rights not
+ expressly granted by Licensor are hereby reserved.
+ ․
+ 4. Restrictions. The license granted in Section 3 above is expressly
+ made subject to and limited by the following restrictions:
+ ․
+ a. You may Distribute or Publicly Perform the Work only under the terms
+ of this License. You must include a copy of, or the Uniform Resource
+ Identifier (URI) for, this License with every copy of the Work You
+ Distribute or Publicly Perform. You may not offer or impose any terms on
+ the Work that restrict the terms of this License or the ability of the
+ recipient of the Work to exercise the rights granted to that recipient
+ under the terms of the License. You may not sublicense the Work. You
+ must keep intact all notices that refer to this License and to the
+ disclaimer of warranties with every copy of the Work You Distribute or
+ Publicly Perform. When You Distribute or Publicly Perform the Work, You
+ may not impose any effective technological measures on the Work that
+ restrict the ability of a recipient of the Work from You to exercise the
+ rights granted to that recipient under the terms of the License. This
+ Section 4(a) applies to the Work as incorporated in a Collection, but
+ this does not require the Collection apart from the Work itself to be
+ made subject to the terms of this License. If You create a Collection,
+ upon notice from any Licensor You must, to the extent practicable,
+ remove from the Collection any credit as required by Section 4(c), as
+ requested. If You create an Adaptation, upon notice from any Licensor
+ You must, to the extent practicable, remove from the Adaptation any
+ credit as required by Section 4(c), as requested.
+ ․
+ b. You may Distribute or Publicly Perform an Adaptation only under the
+ terms of: (i) this License; (ii) a later version of this License with
+ the same License Elements as this License; (iii) a Creative Commons
+ jurisdiction license (either this or a later license version) that
+ contains the same License Elements as this License (e.g.,
+ Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
+ License. If you license the Adaptation under one of the licenses
+ mentioned in (iv), you must comply with the terms of that license. If
+ you license the Adaptation under the terms of any of the licenses
+ mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
+ comply with the terms of the Applicable License generally and the
+ following provisions: (I) You must include a copy of, or the URI for,
+ the Applicable License with every copy of each Adaptation You Distribute
+ or Publicly Perform; (II) You may not offer or impose any terms on the
+ Adaptation that restrict the terms of the Applicable License or the
+ ability of the recipient of the Adaptation to exercise the rights
+ granted to that recipient under the terms of the Applicable License;
+ (III) You must keep intact all notices that refer to the Applicable
+ License and to the disclaimer of warranties with every copy of the Work
+ as included in the Adaptation You Distribute or Publicly Perform; (IV)
+ when You Distribute or Publicly Perform the Adaptation, You may not
+ impose any effective technological measures on the Adaptation that
+ restrict the ability of a recipient of the Adaptation from You to
+ exercise the rights granted to that recipient under the terms of the
+ Applicable License. This Section 4(b) applies to the Adaptation as
+ incorporated in a Collection, but this does not require the Collection
+ apart from the Adaptation itself to be made subject to the terms of the
+ Applicable License.
+ ․
+ c. If You Distribute, or Publicly Perform the Work or any Adaptations or
+ Collections, You must, unless a request has been made pursuant to
+ Section 4(a), keep intact all copyright notices for the Work and
+ provide, reasonable to the medium or means You are utilizing: (i) the
+ name of the Original Author (or pseudonym, if applicable) if supplied,
+ and/or if the Original Author and/or Licensor designate another party or
+ parties (e.g., a sponsor institute, publishing entity, journal) for
+ attribution ("Attribution Parties") in Licensor's copyright notice,
+ terms of service or by other reasonable means, the name of such party or
+ parties; (ii) the title of the Work if supplied; (iii) to the extent
+ reasonably practicable, the URI, if any, that Licensor specifies to be
+ associated with the Work, unless such URI does not refer to the
+ copyright notice or licensing information for the Work; and (iv) ,
+ consistent with Ssection 3(b), in the case of an Adaptation, a credit
+ identifying the use of the Work in the Adaptation (e.g., "French
+ translation of the Work by Original Author," or "Screenplay based on
+ original Work by Original Author"). The credit required by this Section
+ 4(c) may be implemented in any reasonable manner; provided, however,
+ that in the case of a Adaptation or Collection, at a minimum such credit
+ will appear, if a credit for all contributing authors of the Adaptation
+ or Collection appears, then as part of these credits and in a manner at
+ least as prominent as the credits for the other contributing authors.
+ For the avoidance of doubt, You may only use the credit required by this
+ Section for the purpose of attribution in the manner set out above and,
+ by exercising Your rights under this License, You may not implicitly or
+ explicitly assert or imply any connection with, sponsorship or
+ endorsement by the Original Author, Licensor and/or Attribution Parties,
+ as appropriate, of You or Your use of the Work, without the separate,
+ express prior written permission of the Original Author, Licensor and/or
+ Attribution Parties.
+ ․
+ d. Except as otherwise agreed in writing by the Licensor or as may be
+ otherwise permitted by applicable law, if You Reproduce, Distribute or
+ Publicly Perform the Work either by itself or as part of any Adaptations
+ or Collections, You must not distort, mutilate, modify or take other
+ derogatory action in relation to the Work which would be prejudicial to
+ the Original Author's honor or reputation. Licensor agrees that in those
+ jurisdictions (e.g. Japan), in which any exercise of the right granted
+ in Section 3(b) of this License (the right to make Adaptations) would be
+ deemed to be a distortion, mutilation, modification or other derogatory
+ action prejudicial to the Original Author's honor and reputation, the
+ Licensor will waive or not assert, as appropriate, this Section, to the
+ fullest extent permitted by the applicable national law, to enable You
+ to reasonably exercise Your right under Section 3(b) of this License
+ (right to make Adaptations) but not otherwise.
+ ․
+ 5. Representations, Warranties and Disclaimer
+ ․
+ UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+ OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
+ KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
+ INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
+ FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
+ LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
+ WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+ ․
+ 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
+ LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
+ ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
+ ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
+ BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ ․
+ 7. Termination
+ ․
+ a. This License and the rights granted hereunder will terminate
+ automatically upon any breach by You of the terms of this License.
+ Individuals or entities who have received Adaptations or Collections
+ from You under this License, however, will not have their licenses
+ terminated provided such individuals or entities remain in full
+ compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
+ survive any termination of this License.
+ ․
+ b. Subject to the above terms and conditions, the license granted here
+ is perpetual (for the duration of the applicable copyright in the Work).
+ Notwithstanding the above, Licensor reserves the right to release the
+ Work under different license terms or to stop distributing the Work at
+ any time; provided, however that any such election will not serve to
+ withdraw this License (or any other license that has been, or is
+ required to be, granted under the terms of this License), and this
+ License will continue in full force and effect unless terminated as
+ stated above.
+ ․
+ 8. Miscellaneous
+ ․
+ a. Each time You Distribute or Publicly Perform the Work or a
+ Collection, the Licensor offers to the recipient a license to the Work
+ on the same terms and conditions as the license granted to You under
+ this License.
+ ․
+ b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
+ offers to the recipient a license to the original Work on the same terms
+ and conditions as the license granted to You under this License.
+ ․
+ c. If any provision of this License is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of
+ the remainder of the terms of this License, and without further action
+ by the parties to this agreement, such provision shall be reformed to
+ the minimum extent necessary to make such provision valid and
+ enforceable.
+ ․
+ d. No term or provision of this License shall be deemed waived and no
+ breach consented to unless such waiver or consent shall be in writing
+ and signed by the party to be charged with such waiver or consent.
+ ․
+ e. This License constitutes the entire agreement between the parties
+ with respect to the Work licensed here. There are no understandings,
+ agreements or representations with respect to the Work not specified
+ here. Licensor shall not be bound by any additional provisions that may
+ appear in any communication from You. This License may not be modified
+ without the mutual written agreement of the Licensor and You.
+ ․
+ f. The rights granted under, and the subject matter referenced, in this
+ License were drafted utilizing the terminology of the Berne Convention
+ for the Protection of Literary and Artistic Works (as amended on
+ September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
+ Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and
+ the Universal Copyright Convention (as revised on July 24, 1971). These
+ rights and subject matter take effect in the relevant jurisdiction in
+ which the License terms are sought to be enforced according to the
+ corresponding provisions of the implementation of those treaty
+ provisions in the applicable national law. If the standard suite of
+ rights granted under applicable copyright law includes additional rights
+ not granted under this License, such additional rights are deemed to be
+ included in the License; this License is not intended to restrict the
+ license of any rights under applicable law.
+ ․
+ ․
+ Creative Commons Notice
+ ․
+ Creative Commons is not a party to this License, and makes no warranty
+ whatsoever in connection with the Work. Creative Commons will not be
+ liable to You or any party on any legal theory for any damages
+ whatsoever, including without limitation any general, special,
+ incidental or consequential damages arising in connection to this
+ license. Notwithstanding the foregoing two (2) sentences, if Creative
+ Commons has expressly identified itself as the Licensor hereunder, it
+ shall have all rights and obligations of Licensor.
+ ․
+ Except for the limited purpose of indicating to the public that the Work
+ is licensed under the CCPL, Creative Commons does not authorize the use
+ by either party of the trademark "Creative Commons" or any related
+ trademark or logo of Creative Commons without the prior written consent
+ of Creative Commons. Any permitted use will be in compliance with
+ Creative Commons' then-current trademark usage guidelines, as may be
+ published on its website or otherwise made available upon request from
+ time to time. For the avoidance of doubt, this trademark restriction
+ does not form part of the License.
+ ․
+ Creative Commons may be contacted at http://creativecommons.org/.
+
+License: BSD-3-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ .
+   1. Redistributions of source code must retain the above
+      copyright notice, this list of conditions and the following
+      disclaimer.
+ .
+   2. 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.
+ .
+   3. Neither the name of the copyright holder 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 HOLDER 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.
diff --git a/gbp.conf b/gbp.conf
new file mode 100644 (file)
index 0000000..023aff3
--- /dev/null
+++ b/gbp.conf
@@ -0,0 +1,3 @@
+[DEFAULT]
+debian-branch = debian/unstable
+pristine-tar = True
diff --git a/lib-systemd/system-sleep/ceph b/lib-systemd/system-sleep/ceph
new file mode 100755 (executable)
index 0000000..cf62f83
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+#/lib/systemd/system-sleep/ceph
+
+case $1 in
+pre)
+    /bin/systemctl stop ceph
+;;
+post)
+    /bin/systemctl start ceph
+;;
+esac
diff --git a/lib-systemd/system/ceph-create-keys.service b/lib-systemd/system/ceph-create-keys.service
new file mode 100644 (file)
index 0000000..4e29bc1
--- /dev/null
@@ -0,0 +1,9 @@
+[Unit]
+Description=Create Ceph client.admin key when possible
+PartOf=ceph-mon.service
+
+[Service]
+Environment=CLUSTER=ceph
+Environment=CONFIG=/etc/ceph/ceph.conf
+EnvironmentFile=-/etc/default/ceph
+ExecStart=/usr/sbin/ceph-create-keys --cluster ${CLUSTER} --id %H
diff --git a/lib-systemd/system/ceph-mds.service b/lib-systemd/system/ceph-mds.service
new file mode 100644 (file)
index 0000000..86ff057
--- /dev/null
@@ -0,0 +1,16 @@
+[Unit]
+Description=Ceph metadata server daemon (MDS)
+Documentation=man:ceph-mds
+After=network-online.target nss-lookup.target
+Wants=network-online.target nss-lookup.target
+PartOf=ceph.target
+
+[Service]
+LimitNOFILE=1048576
+LimitNPROC=1048576
+EnvironmentFile=-/etc/default/ceph
+Environment=CLUSTER=ceph
+ExecStart=/usr/bin/ceph-mds -f --cluster ${CLUSTER} --id %H --setuser ceph --setgroup ceph
+
+[Install]
+WantedBy=multi-user.target
diff --git a/lib-systemd/system/ceph-mon.service b/lib-systemd/system/ceph-mon.service
new file mode 100644 (file)
index 0000000..d89c74a
--- /dev/null
@@ -0,0 +1,22 @@
+[Unit]
+Description=Ceph cluster monitor daemon
+Documentation=man:ceph-mon
+
+After=network-online.target local-fs.target ceph-create-keys.service
+Wants=network-online.target local-fs.target ceph-create-keys.service
+
+PartOf=ceph.target
+
+[Service]
+LimitNOFILE=1048576
+LimitNPROC=1048576
+EnvironmentFile=-/etc/default/ceph
+Environment=CLUSTER=ceph
+ExecStart=/usr/bin/ceph-mon -f --cluster ${CLUSTER} --id %H --setuser ceph --setgroup ceph
+ExecReload=/bin/kill -HUP $MAINPID
+Restart=on-failure
+RestartSec=30
+TasksMax=infinity
+
+[Install]
+WantedBy=multi-user.target
diff --git a/lib-systemd/system/ceph-osd@.service b/lib-systemd/system/ceph-osd@.service
new file mode 100644 (file)
index 0000000..adfa6a0
--- /dev/null
@@ -0,0 +1,22 @@
+[Unit]
+Description=Ceph object storage daemon (OSD)
+Documentation=man:ceph-osd
+After=network-online.target
+Wants=network-online.target
+PartOf=ceph.service
+RequiresMountsFor=/var/lib/ceph/osd/ceph-%i
+
+[Service]
+Environment=CLUSTER=ceph
+Environment=CONFIG=/etc/ceph/ceph.conf
+EnvironmentFile=-/etc/default/ceph
+ExecStartPre=-/bin/sh -c '${osd_prestart_sh}' -- %i
+ExecStartPre=/usr/lib/ceph/ceph-osd-prestart.sh --id %i --cluster ${CLUSTER}
+ExecStart=/usr/bin/ceph-osd --id %i --foreground --cluster ${CLUSTER} -c ${CONFIG}
+ExecStopPost=-/bin/sh -c '${osd_poststop_sh}' -- %i
+LimitNOFILE=327680
+Restart=on-failure
+RestartSec=30
+
+[Install]
+WantedBy=multi-user.target
diff --git a/libcephfs-dev.install b/libcephfs-dev.install
new file mode 100644 (file)
index 0000000..a23c5b0
--- /dev/null
@@ -0,0 +1,2 @@
+usr/include/cephfs/*.h
+usr/lib/*/libcephfs.so
diff --git a/libcephfs-java.jlibs b/libcephfs-java.jlibs
new file mode 100644 (file)
index 0000000..1029fb5
--- /dev/null
@@ -0,0 +1 @@
+debian/tmp/usr/share/java/libcephfs.jar
diff --git a/libcephfs-jni.install b/libcephfs-jni.install
new file mode 100644 (file)
index 0000000..15cb91d
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libcephfs_jni.so* usr/lib/jni
diff --git a/libcephfs-jni.lintian-overrides b/libcephfs-jni.lintian-overrides
new file mode 100644 (file)
index 0000000..93a0dac
--- /dev/null
@@ -0,0 +1 @@
+binary-or-shlib-defines-rpath usr/lib/jni/*
diff --git a/libcephfs2.install b/libcephfs2.install
new file mode 100644 (file)
index 0000000..f09e93c
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libcephfs.so.*
diff --git a/libcephfs2.lintian-overrides b/libcephfs2.lintian-overrides
new file mode 100644 (file)
index 0000000..d4041ca
--- /dev/null
@@ -0,0 +1,2 @@
+# False-positives:
+spelling-error-in-binary * tEH the
diff --git a/libcephfs2.symbols b/libcephfs2.symbols
new file mode 100644 (file)
index 0000000..b94b81d
--- /dev/null
@@ -0,0 +1,186 @@
+libcephfs.so.2 libcephfs2 #MINVER#
+ (regex|c++)"^_.*" 12.0.3
+ ceph_abort_conn@Base 14.2.0
+ ceph_buffer_free@Base 12.0.3
+ ceph_chdir@Base 12.0.3
+ ceph_chmod@Base 12.0.3
+ ceph_chown@Base 12.0.3
+ ceph_close@Base 12.0.3
+ ceph_closedir@Base 12.0.3
+ ceph_conf_get@Base 12.0.3
+ ceph_conf_parse_argv@Base 12.0.3
+ ceph_conf_parse_env@Base 12.0.3
+ ceph_conf_read_file@Base 12.0.3
+ ceph_conf_set@Base 12.0.3
+ ceph_create@Base 12.0.3
+ ceph_create_from_rados@Base 12.0.3
+ ceph_create_with_context@Base 12.0.3
+ ceph_debug_get_fd_caps@Base 12.0.3
+ ceph_debug_get_file_caps@Base 12.0.3
+ ceph_fallocate@Base 12.0.3
+ ceph_fchmod@Base 12.0.3
+ ceph_fchown@Base 12.0.3
+ ceph_fgetxattr@Base 12.0.3
+ ceph_finish_reclaim@Base 14.2.0
+ ceph_flistxattr@Base 12.0.3
+ ceph_flock@Base 12.0.3
+ ceph_fremovexattr@Base 12.0.3
+ ceph_fsetattrx@Base 12.0.3
+ ceph_fsetxattr@Base 12.0.3
+ ceph_fstat@Base 14.2.0
+ ceph_fstatx@Base 12.0.3
+ ceph_fsync@Base 12.0.3
+ ceph_ftruncate@Base 12.0.3
+ ceph_futime@Base 14.2.0
+ ceph_futimens@Base 14.2.0
+ ceph_futimes@Base 14.2.0
+ ceph_get_cap_return_timeout@Base 13.2.0
+ ceph_get_default_data_pool_name@Base 12.1.4
+ ceph_get_file_extent_osds@Base 12.0.3
+ ceph_get_file_layout@Base 12.0.3
+ ceph_get_file_object_size@Base 12.0.3
+ ceph_get_file_pool@Base 12.0.3
+ ceph_get_file_pool_name@Base 12.0.3
+ ceph_get_file_replication@Base 12.0.3
+ ceph_get_file_stripe_address@Base 12.0.3
+ ceph_get_file_stripe_count@Base 12.0.3
+ ceph_get_file_stripe_unit@Base 12.0.3
+ ceph_get_fs_cid@Base 14.2.0
+ ceph_get_instance_id@Base 14.2.0
+ ceph_get_local_osd@Base 12.0.3
+ ceph_get_mount_context@Base 12.0.3
+ ceph_get_osd_addr@Base 12.0.3
+ ceph_get_osd_crush_location@Base 12.0.3
+ ceph_get_path_layout@Base 12.0.3
+ ceph_get_path_object_size@Base 12.0.3
+ ceph_get_path_pool@Base 12.0.3
+ ceph_get_path_pool_name@Base 12.0.3
+ ceph_get_path_replication@Base 12.0.3
+ ceph_get_path_stripe_count@Base 12.0.3
+ ceph_get_path_stripe_unit@Base 12.0.3
+ ceph_get_pool_id@Base 12.0.3
+ ceph_get_pool_name@Base 12.0.3
+ ceph_get_pool_replication@Base 12.0.3
+ ceph_get_stripe_unit_granularity@Base 12.0.3
+ ceph_getcwd@Base 12.0.3
+ ceph_getdents@Base 12.0.3
+ ceph_getdnames@Base 12.0.3
+ ceph_getxattr@Base 12.0.3
+ ceph_init@Base 12.0.3
+ ceph_is_mounted@Base 12.0.3
+ ceph_lazyio@Base 14.2.0
+ ceph_lazyio_propagate@Base 14.2.15
+ ceph_lazyio_synchronize@Base 14.2.15
+ ceph_lchown@Base 12.0.3
+ ceph_lgetxattr@Base 12.0.3
+ ceph_link@Base 12.0.3
+ ceph_listxattr@Base 12.0.3
+ ceph_ll_close@Base 12.0.3
+ ceph_ll_commit_blocks@Base 12.0.3
+ ceph_ll_create@Base 12.0.3
+ ceph_ll_delegation@Base 13.2.0
+ ceph_ll_fallocate@Base 14.2.0
+ ceph_ll_file_layout@Base 12.0.3
+ ceph_ll_forget@Base 12.0.3
+ ceph_ll_fsync@Base 12.0.3
+ ceph_ll_get_inode@Base 12.0.3
+ ceph_ll_get_internal_offset@Base 12.0.3
+ ceph_ll_get_stripe_osd@Base 12.0.3
+ ceph_ll_getattr@Base 12.0.3
+ ceph_ll_getlk@Base 12.0.3
+ ceph_ll_getxattr@Base 12.0.3
+ ceph_ll_lazyio@Base 14.2.0
+ ceph_ll_link@Base 12.0.3
+ ceph_ll_listxattr@Base 12.0.3
+ ceph_ll_lookup@Base 12.0.3
+ ceph_ll_lookup_inode@Base 12.0.3
+ ceph_ll_lookup_root@Base 12.0.3
+ ceph_ll_lseek@Base 12.0.3
+ ceph_ll_mkdir@Base 12.0.3
+ ceph_ll_mknod@Base 12.0.3
+ ceph_ll_num_osds@Base 12.0.3
+ ceph_ll_open@Base 12.0.3
+ ceph_ll_opendir@Base 12.0.3
+ ceph_ll_osdaddr@Base 12.0.3
+ ceph_ll_put@Base 12.0.3
+ ceph_ll_read@Base 12.0.3
+ ceph_ll_read_block@Base 12.0.3
+ ceph_ll_readlink@Base 12.0.3
+ ceph_ll_readv@Base 12.0.3
+ ceph_ll_register_callbacks@Base 14.2.15
+ ceph_ll_releasedir@Base 12.0.3
+ ceph_ll_removexattr@Base 12.0.3
+ ceph_ll_rename@Base 12.0.3
+ ceph_ll_rmdir@Base 12.0.3
+ ceph_ll_setattr@Base 12.0.3
+ ceph_ll_setlk@Base 12.0.3
+ ceph_ll_setxattr@Base 12.0.3
+ ceph_ll_snap_seq@Base 12.0.3
+ ceph_ll_statfs@Base 12.0.3
+ ceph_ll_stripe_unit@Base 12.0.3
+ ceph_ll_symlink@Base 12.0.3
+ ceph_ll_sync_inode@Base 13.2.0
+ ceph_ll_unlink@Base 12.0.3
+ ceph_ll_walk@Base 12.0.3
+ ceph_ll_write@Base 12.0.3
+ ceph_ll_write_block@Base 12.0.3
+ ceph_ll_writev@Base 12.0.3
+ ceph_llistxattr@Base 12.0.3
+ ceph_localize_reads@Base 12.0.3
+ ceph_lremovexattr@Base 12.0.3
+ ceph_lseek@Base 12.0.3
+ ceph_lsetxattr@Base 12.0.3
+ ceph_lstat@Base 14.2.0
+ ceph_lutimes@Base 14.2.0
+ ceph_mds_command@Base 12.0.3
+ ceph_mkdir@Base 12.0.3
+ ceph_mkdirs@Base 12.0.3
+ ceph_mknod@Base 12.0.3
+ ceph_mount@Base 12.0.3
+ ceph_mount_perms@Base 12.0.3
+ ceph_mount_perms_set@Base 13.2.0
+ ceph_open@Base 12.0.3
+ ceph_open_layout@Base 12.0.3
+ ceph_opendir@Base 12.0.3
+ ceph_preadv@Base 12.0.3
+ ceph_pwritev@Base 12.0.3
+ ceph_read@Base 12.0.3
+ ceph_readdir@Base 12.0.3
+ ceph_readdir_r@Base 12.0.3
+ ceph_readdirplus_r@Base 12.0.3
+ ceph_readlink@Base 12.0.3
+ ceph_release@Base 12.0.3
+ ceph_removexattr@Base 12.0.3
+ ceph_rename@Base 12.0.3
+ ceph_rewinddir@Base 12.0.3
+ ceph_rmdir@Base 12.0.3
+ ceph_seekdir@Base 12.0.3
+ ceph_select_filesystem@Base 14.2.0
+ ceph_set_default_file_replication@Base 12.0.3
+ ceph_set_default_file_stripe_count@Base 12.0.3
+ ceph_set_default_file_stripe_unit@Base 12.0.3
+ ceph_set_default_object_size@Base 12.0.3
+ ceph_set_default_preferred_pg@Base 12.0.3
+ ceph_set_deleg_timeout@Base 13.2.0
+ ceph_set_session_timeout@Base 14.2.0
+ ceph_set_uuid@Base 14.2.0
+ ceph_setattrx@Base 12.0.3
+ ceph_setxattr@Base 12.0.3
+ ceph_shutdown@Base 12.0.3
+ ceph_start_reclaim@Base 14.2.0
+ ceph_stat@Base 14.2.0
+ ceph_statfs@Base 12.0.3
+ ceph_statx@Base 12.0.3
+ ceph_symlink@Base 12.0.3
+ ceph_sync_fs@Base 12.0.3
+ ceph_telldir@Base 12.0.3
+ ceph_truncate@Base 12.0.3
+ ceph_umask@Base 14.2.0
+ ceph_unlink@Base 12.0.3
+ ceph_unmount@Base 12.0.3
+ ceph_userperm_destroy@Base 12.0.3
+ ceph_userperm_new@Base 12.0.3
+ ceph_utime@Base 12.0.3
+ ceph_utimes@Base 14.2.0
+ ceph_version@Base 12.0.3
+ ceph_write@Base 12.0.3
diff --git a/librados-dev.install b/librados-dev.install
new file mode 100644 (file)
index 0000000..edaa359
--- /dev/null
@@ -0,0 +1,5 @@
+usr/bin/librados-config
+usr/include/rados/librados.h
+usr/include/rados/rados_types.h
+usr/lib/*/librados.so
+usr/share/man/man8/librados-config.8
diff --git a/librados2.install b/librados2.install
new file mode 100644 (file)
index 0000000..2020e29
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/*/ceph/libceph-common.so*
+usr/lib/*/librados.so.*
diff --git a/librados2.lintian-overrides b/librados2.lintian-overrides
new file mode 100644 (file)
index 0000000..d4041ca
--- /dev/null
@@ -0,0 +1,2 @@
+# False-positives:
+spelling-error-in-binary * tEH the
diff --git a/librados2.symbols b/librados2.symbols
new file mode 100644 (file)
index 0000000..6507d90
--- /dev/null
@@ -0,0 +1,423 @@
+libceph-common.so.0 librados2 #MINVER#
+ (regex|c++)"^_.*" 12.0.3
+ (regex)"^ceph_ver__[0-9a-f]{40}@Base$" 12.0.3
+ MDS_GID_NONE@Base 12.0.3
+ XXH32@Base 12.0.3
+ XXH32_canonicalFromHash@Base 12.0.3
+ XXH32_createState@Base 12.0.3
+ XXH32_digest@Base 12.0.3
+ XXH32_freeState@Base 12.0.3
+ XXH32_hashFromCanonical@Base 12.0.3
+ XXH32_reset@Base 12.0.3
+ XXH32_update@Base 12.0.3
+ XXH64@Base 12.0.3
+ XXH64_canonicalFromHash@Base 12.0.3
+ XXH64_createState@Base 12.0.3
+ XXH64_digest@Base 12.0.3
+ XXH64_freeState@Base 12.0.3
+ XXH64_hashFromCanonical@Base 12.0.3
+ XXH64_reset@Base 12.0.3
+ XXH64_update@Base 12.0.3
+ XXH_versionNumber@Base 12.0.3
+ boost_asio_detail_posix_thread_function@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_aesni@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_pclmul@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_probe@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_sse2@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_sse3@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_sse41@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_sse42@Base 12.0.3
+ (arch=amd64 i386)ceph_arch_intel_ssse3@Base 12.0.3
+ (arch=arm64 armhf)ceph_arch_aarch64_crc32@Base 12.0.3
+ (arch=arm64 armhf)ceph_arch_arm_probe@Base 12.0.3
+ (arch=ppc64el)ceph_arch_ppc_crc32@Base 12.0.3
+ (arch=ppc64el)ceph_arch_ppc_probe@Base 12.0.3a
+ (arch=arm64 armhf)ceph_arch_neon@Base 12.0.3
+ ceph_arch_probe@Base 12.0.3
+ ceph_arch_probed@Base 12.0.3
+ ceph_armor@Base 12.0.3
+ ceph_armor_line_break@Base 12.0.3
+ ceph_choose_crc32@Base 12.1.4
+ (arch=arm64)ceph_crc32c_aarch64@Base 12.0.3
+ ceph_crc32c_func@Base 12.0.3
+ (arch=amd64)ceph_crc32c_intel_baseline@Base 12.0.3
+ (arch=amd64 i386)ceph_crc32c_intel_fast@Base 12.0.3
+ (arch=amd64 i386)ceph_crc32c_intel_fast_exists@Base 12.0.3
+ (arch=ppc64el)ceph_crc32c_ppc@Base 12.0.3
+ ceph_crc32c_sctp@Base 12.0.3
+ ceph_crc32c_zeros@Base 12.1.4
+ ceph_options@Base 12.1.4
+ ceph_os_fgetxattr@Base 12.0.3
+ ceph_os_flistxattr@Base 12.0.3
+ ceph_os_fremovexattr@Base 12.0.3
+ ceph_os_fsetxattr@Base 12.0.3
+ ceph_os_getxattr@Base 12.0.3
+ ceph_os_listxattr@Base 12.0.3
+ ceph_os_removexattr@Base 12.0.3
+ ceph_os_setxattr@Base 12.0.3
+ ceph_unarmor@Base 12.0.3
+ check_for_control_characters@Base 12.0.3
+ check_for_control_characters_cstr@Base 12.0.3
+ check_utf8@Base 12.0.3
+ check_utf8_cstr@Base 12.0.3
+ code_environment_to_str@Base 12.0.3
+ (arch=amd64)crc32_iscsi_00@Base 12.0.3
+ (arch=amd64)crc32_iscsi_00_slver@Base 12.0.3
+ (arch=amd64)crc32_iscsi_00_slver_00020014@Base 12.0.3
+ (arch=amd64)crc32_iscsi_zero_00@Base 12.0.3
+ (arch=amd64)crc32_iscsi_zero_00_slver@Base 12.0.3
+ (arch=amd64)crc32_iscsi_zero_00_slver_00020014@Base 12.0.3
+ (arch=amd64)crc32_table_iscsi_base@Base 12.0.3
+ (arch=ppc64el)crc_zero@Base 12.1.4
+ crush_add_bucket@Base 12.0.3
+ crush_add_list_bucket_item@Base 12.0.3
+ crush_add_rule@Base 12.0.3
+ crush_add_straw2_bucket_item@Base 12.0.3
+ crush_add_straw_bucket_item@Base 12.0.3
+ crush_add_tree_bucket_item@Base 12.0.3
+ crush_add_uniform_bucket_item@Base 12.0.3
+ crush_addition_is_unsafe@Base 12.0.3
+ crush_adjust_list_bucket_item_weight@Base 12.0.3
+ crush_adjust_straw2_bucket_item_weight@Base 12.0.3
+ crush_adjust_straw_bucket_item_weight@Base 12.0.3
+ crush_adjust_tree_bucket_item_weight@Base 12.0.3
+ crush_adjust_uniform_bucket_item_weight@Base 12.0.3
+ crush_bucket_add_item@Base 12.0.3
+ crush_bucket_adjust_item_weight@Base 12.0.3
+ crush_bucket_alg_name@Base 12.0.3
+ crush_bucket_remove_item@Base 12.0.3
+ crush_calc_straw@Base 12.0.3
+ crush_create@Base 12.0.3
+ crush_destroy@Base 12.0.3
+ crush_destroy_bucket@Base 12.0.3
+ crush_destroy_bucket_list@Base 12.0.3
+ crush_destroy_bucket_straw2@Base 12.0.3
+ crush_destroy_bucket_straw@Base 12.0.3
+ crush_destroy_bucket_tree@Base 12.0.3
+ crush_destroy_bucket_uniform@Base 12.0.3
+ crush_destroy_choose_args@Base 12.0.3
+ crush_destroy_rule@Base 12.0.3
+ crush_do_rule@Base 12.0.3
+ crush_finalize@Base 12.0.3
+ crush_find_rule@Base 12.0.3
+ crush_get_bucket_item_weight@Base 12.0.3
+ crush_get_next_bucket_id@Base 12.0.3
+ crush_hash32@Base 12.0.3
+ crush_hash32_2@Base 12.0.3
+ crush_hash32_3@Base 12.0.3
+ crush_hash32_4@Base 12.0.3
+ crush_hash32_5@Base 12.0.3
+ crush_hash_name@Base 12.0.3
+ crush_init_workspace@Base 12.0.3
+ crush_make_bucket@Base 12.0.3
+ crush_make_choose_args@Base 12.0.3
+ crush_make_list_bucket@Base 12.0.3
+ crush_make_rule@Base 12.0.3
+ crush_make_straw2_bucket@Base 12.0.3
+ crush_make_straw_bucket@Base 12.0.3
+ crush_make_tree_bucket@Base 12.0.3
+ crush_make_uniform_bucket@Base 12.0.3
+ crush_multiplication_is_unsafe@Base 12.0.3
+ crush_remove_bucket@Base 12.0.3
+ crush_remove_list_bucket_item@Base 12.0.3
+ crush_remove_straw2_bucket_item@Base 12.0.3
+ crush_remove_straw_bucket_item@Base 12.0.3
+ crush_remove_tree_bucket_item@Base 12.0.3
+ crush_remove_uniform_bucket_item@Base 12.0.3
+ crush_reweight_bucket@Base 12.0.3
+ crush_rule_set_step@Base 12.0.3
+ current_maxid@Base 12.0.3
+ decode_utf8@Base 12.0.3
+ encode_utf8@Base 12.0.3
+ g_assert_condition@Base 14.2.0
+ g_assert_file@Base 14.2.0
+ g_assert_func@Base 14.2.0
+ g_assert_line@Base 14.2.0
+ g_assert_msg@Base 14.2.0
+ g_assert_thread@Base 14.2.0
+ g_assert_thread_name@Base 14.2.0
+ g_ceph_context@Base 12.0.3
+ g_code_env@Base 12.0.3
+ g_eio@Base 14.2.0
+ g_eio_devname@Base 14.2.0
+ g_eio_error@Base 14.2.0
+ g_eio_iotype@Base 14.2.0
+ g_eio_length@Base 14.2.0
+ g_eio_offset@Base 14.2.0
+ g_eio_path@Base 14.2.0
+ g_lockdep@Base 12.0.3
+ g_process_name@Base 14.2.0
+ get_linux_version@Base 12.0.3
+ get_process_name@Base 12.0.3
+ is_control_character@Base 12.0.3
+ last_freed_id@Base 12.0.3
+ mime_decode_from_qp@Base 12.0.3
+ mime_encode_as_qp@Base 12.0.3
+ module_has_param@Base 12.0.3
+ module_load@Base 12.0.3
+ pem_key@Base 12.0.3
+ resolve_addrs@Base 12.0.3
+ reverse_bits@Base 12.1.4
+ reverse_nibbles@Base 12.1.4
+ safe_cat@Base 12.0.3
+ safe_pread@Base 12.0.3
+ safe_pread_exact@Base 12.0.3
+ safe_pwrite@Base 12.0.3
+ safe_read@Base 12.0.3
+ safe_read_exact@Base 12.0.3
+ safe_read_file@Base 12.0.3
+ safe_splice@Base 12.0.3
+ safe_splice_exact@Base 12.0.3
+ safe_write@Base 12.0.3
+ safe_write_file@Base 12.0.3
+ sctp_crc_c@Base 12.0.3
+ sctp_crc_tableil8_o32@Base 12.0.3
+ sctp_crc_tableil8_o40@Base 12.0.3
+ sctp_crc_tableil8_o48@Base 12.0.3
+ sctp_crc_tableil8_o56@Base 12.0.3
+ sctp_crc_tableil8_o64@Base 12.0.3
+ sctp_crc_tableil8_o72@Base 12.0.3
+ sctp_crc_tableil8_o80@Base 12.0.3
+ sctp_crc_tableil8_o88@Base 12.0.3
+ set_legacy_crush_map@Base 12.0.3
+ set_optimal_crush_map@Base 12.0.3
+librados.so.2 librados2 #MINVER#
+ (symver)LIBRADOS_14.2.0 14.2.0
+ (symver)LIBRADOS_PRIVATE 14.2.0
+ rados_aio_append@Base 0.72.2
+ rados_aio_cancel@Base 0.87
+ rados_aio_cmpext@Base 12.0.3
+ rados_aio_create_completion@Base 0.72.2
+ rados_aio_exec@Base 12.0.3
+ rados_aio_flush@Base 0.72.2
+ rados_aio_flush_async@Base 0.72.2
+ rados_aio_get_return_value@Base 0.72.2
+ rados_aio_get_version@Base 12.0.3
+ rados_aio_getxattr@Base 12.0.3
+ rados_aio_getxattrs@Base 12.0.3
+ rados_aio_ioctx_selfmanaged_snap_create@Base 12.0.3
+ rados_aio_ioctx_selfmanaged_snap_remove@Base 12.0.3
+ rados_aio_is_complete@Base 0.72.2
+ rados_aio_is_complete_and_cb@Base 0.72.2
+ rados_aio_is_safe@Base 0.72.2
+ rados_aio_is_safe_and_cb@Base 0.72.2
+ rados_aio_notify@Base 10.1.0
+ rados_aio_read@Base 0.72.2
+ rados_aio_read_op_operate@Base 0.79
+ rados_aio_release@Base 0.72.2
+ rados_aio_remove@Base 0.72.2
+ rados_aio_rmxattr@Base 12.0.3
+ rados_aio_setxattr@Base 12.0.3
+ rados_aio_stat@Base 0.72.2
+ rados_aio_unlock@Base 12.0.3
+ rados_aio_unwatch@Base 10.1.0
+ rados_aio_wait_for_complete@Base 0.72.2
+ rados_aio_wait_for_complete_and_cb@Base 0.72.2
+ rados_aio_wait_for_safe@Base 0.72.2
+ rados_aio_wait_for_safe_and_cb@Base 0.72.2
+ rados_aio_watch2@Base 12.0.3
+ rados_aio_watch@Base 10.1.0
+ rados_aio_watch_flush@Base 10.1.0
+ rados_aio_write@Base 0.72.2
+ rados_aio_write_full@Base 0.72.2
+ rados_aio_write_op_operate@Base 0.79
+ rados_aio_writesame@Base 12.0.3
+ rados_append@Base 0.72.2
+ rados_application_enable@Base 12.1.4
+ rados_application_list@Base 12.1.4
+ rados_application_metadata_get@Base 12.1.4
+ rados_application_metadata_list@Base 12.1.4
+ rados_application_metadata_remove@Base 12.1.4
+ rados_application_metadata_set@Base 12.1.4
+ rados_blacklist_add@Base 0.93
+ rados_break_lock@Base 0.72.2
+ rados_buffer_free@Base 0.72.2
+ rados_cache_pin@Base 10.1.0
+ rados_cache_unpin@Base 10.1.0
+ rados_cct@Base 0.72.2
+ rados_checksum@Base 12.0.3
+ rados_cluster_fsid@Base 0.72.2
+ rados_cluster_stat@Base 0.72.2
+ rados_cmpext@Base 12.0.3
+ rados_conf_get@Base 0.72.2
+ rados_conf_parse_argv@Base 0.72.2
+ rados_conf_parse_argv_remainder@Base 0.72.2
+ rados_conf_parse_env@Base 0.72.2
+ rados_conf_read_file@Base 0.72.2
+ rados_conf_set@Base 0.72.2
+ rados_connect@Base 0.72.2
+ rados_create2@Base 0.72.2
+ rados_create@Base 0.72.2
+ rados_create_read_op@Base 0.79
+ rados_create_with_context@Base 0.72.2
+ rados_create_write_op@Base 0.79
+ rados_exec@Base 0.72.2
+ rados_get_instance_id@Base 0.72.2
+ rados_get_last_version@Base 0.72.2
+ rados_get_min_compatible_client@Base 13.2.0
+ rados_get_min_compatible_osd@Base 14.2.0
+ rados_getaddrs@Base 14.2.15
+ rados_getxattr@Base 0.72.2
+ rados_getxattrs@Base 0.72.2
+ rados_getxattrs_end@Base 0.72.2
+ rados_getxattrs_next@Base 0.72.2
+ rados_inconsistent_pg_list@Base 10.1.0
+ rados_ioctx_cct@Base 0.72.2
+ rados_ioctx_create2@Base 0.93
+ rados_ioctx_create@Base 0.72.2
+ rados_ioctx_destroy@Base 0.72.2
+ rados_ioctx_get_cluster@Base 0.72.2
+ rados_ioctx_get_id@Base 0.72.2
+ rados_ioctx_get_namespace@Base 14.2.0
+ rados_ioctx_get_pool_name@Base 0.72.2
+ rados_ioctx_locator_set_key@Base 0.72.2
+ rados_ioctx_pool_get_auid@Base 0.72.2
+ rados_ioctx_pool_required_alignment2@Base 10.1.0
+ rados_ioctx_pool_required_alignment@Base 0.79
+ rados_ioctx_pool_requires_alignment2@Base 10.1.0
+ rados_ioctx_pool_requires_alignment@Base 0.79
+ rados_ioctx_pool_set_auid@Base 0.72.2
+ rados_ioctx_pool_stat@Base 0.72.2
+ rados_ioctx_pool_stat@LIBRADOS_14.2.0 14.2.0
+ rados_ioctx_selfmanaged_snap_create@Base 0.72.2
+ rados_ioctx_selfmanaged_snap_remove@Base 0.72.2
+ rados_ioctx_selfmanaged_snap_rollback@Base 0.72.2
+ rados_ioctx_selfmanaged_snap_set_write_ctx@Base 0.72.2
+ rados_ioctx_set_namespace@Base 0.72.2
+ rados_ioctx_snap_create@Base 0.72.2
+ rados_ioctx_snap_get_name@Base 0.72.2
+ rados_ioctx_snap_get_stamp@Base 0.72.2
+ rados_ioctx_snap_list@Base 0.72.2
+ rados_ioctx_snap_lookup@Base 0.72.2
+ rados_ioctx_snap_remove@Base 0.72.2
+ rados_ioctx_snap_rollback@Base 0.80~rc1
+ rados_ioctx_snap_set_read@Base 0.72.2
+ rados_list_lockers@Base 0.72.2
+ rados_lock_exclusive@Base 0.72.2
+ rados_lock_shared@Base 0.72.2
+ rados_mgr_command@Base 12.0.3
+ rados_mon_command@Base 0.72.2
+ rados_mon_command_target@Base 0.72.2
+ rados_monitor_log2@Base 12.1.4
+ rados_monitor_log@Base 0.72.2
+ rados_nobjects_list_close@Base 0.93
+ rados_nobjects_list_get_cursor@Base 12.0.3
+ rados_nobjects_list_get_pg_hash_position@Base 0.93
+ rados_nobjects_list_next@Base 0.93
+ rados_nobjects_list_open@Base 0.93
+ rados_nobjects_list_seek@Base 0.93
+ rados_nobjects_list_seek_cursor@Base 12.0.3
+ rados_notify2@Base 0.93
+ rados_notify@Base 0.72.2
+ rados_notify_ack@Base 0.93
+ rados_object_list@Base 10.1.0
+ rados_object_list_begin@Base 10.1.0
+ rados_object_list_cursor_cmp@Base 10.1.0
+ rados_object_list_cursor_free@Base 10.1.0
+ rados_object_list_end@Base 10.1.0
+ rados_object_list_free@Base 10.1.0
+ rados_object_list_is_end@Base 10.1.0
+ rados_object_list_slice@Base 10.1.0
+ rados_objects_list_close@Base 0.72.2
+ rados_objects_list_get_pg_hash_position@Base 0.79
+ rados_objects_list_next@Base 0.72.2
+ rados_objects_list_open@Base 0.72.2
+ rados_objects_list_seek@Base 0.79
+ rados_omap_get_end@Base 0.79
+ rados_omap_get_next2@Base 13.2.0
+ rados_omap_get_next@Base 0.79
+ rados_omap_iter_size@Base 14.2.0
+ rados_osd_command@Base 0.72.2
+ rados_pg_command@Base 0.72.2
+ rados_ping_monitor@Base 0.72.2
+ rados_pool_create@Base 0.72.2
+ rados_pool_create_with_all@Base 0.72.2
+ rados_pool_create_with_auid@Base 0.72.2
+ rados_pool_create_with_crush_rule@Base 0.72.2
+ rados_pool_delete@Base 0.72.2
+ rados_pool_get_base_tier@Base 0.93
+ rados_pool_list@Base 0.72.2
+ rados_pool_lookup@Base 0.72.2
+ rados_pool_reverse_lookup@Base 0.72.2
+ rados_read@Base 0.72.2
+ rados_read_op_assert_exists@Base 0.79
+ rados_read_op_assert_version@Base 0.93
+ rados_read_op_checksum@Base 12.0.3
+ rados_read_op_cmpext@Base 12.0.3
+ rados_read_op_cmpxattr@Base 0.79
+ rados_read_op_exec@Base 0.79
+ rados_read_op_exec_user_buf@Base 0.79
+ rados_read_op_getxattrs@Base 0.79
+ rados_read_op_omap_cmp2@Base 13.2.0
+ rados_read_op_omap_cmp@Base 0.79
+ rados_read_op_omap_get_keys2@Base 12.0.3
+ rados_read_op_omap_get_keys@Base 0.79
+ rados_read_op_omap_get_vals2@Base 12.0.3
+ rados_read_op_omap_get_vals@Base 0.79
+ rados_read_op_omap_get_vals_by_keys2@Base 13.2.0
+ rados_read_op_omap_get_vals_by_keys@Base 0.79
+ rados_read_op_operate@Base 0.79
+ rados_read_op_read@Base 0.79
+ rados_read_op_set_flags@Base 0.79
+ rados_read_op_stat@Base 0.79
+ rados_release_read_op@Base 0.79
+ rados_release_write_op@Base 0.79
+ rados_remove@Base 0.72.2
+ rados_rmxattr@Base 0.72.2
+ rados_rollback@Base 0.72.2
+ rados_service_register@Base 12.1.4
+ rados_service_update_status@Base 12.1.4
+ rados_set_alloc_hint2@Base 12.0.3
+ rados_set_alloc_hint@Base 0.79
+ rados_set_osdmap_full_try@Base 13.2.0
+ rados_setxattr@Base 0.72.2
+ rados_shutdown@Base 0.72.2
+ rados_stat@Base 0.72.2
+ rados_tmap_get@Base 0.72.2
+ rados_tmap_get@LIBRADOS_14.2.0 14.2.0
+ rados_tmap_put@Base 0.72.2
+ rados_tmap_put@LIBRADOS_14.2.0 14.2.0
+ rados_tmap_update@Base 0.72.2
+ rados_tmap_update@LIBRADOS_14.2.0 14.2.0
+ rados_trunc@Base 0.72.2
+ rados_unlock@Base 0.72.2
+ rados_unset_osdmap_full_try@Base 13.2.0
+ rados_unwatch2@Base 0.93
+ rados_unwatch@Base 0.72.2
+ rados_version@Base 0.72.2
+ rados_wait_for_latest_osdmap@Base 0.79
+ rados_watch2@Base 0.93
+ rados_watch3@Base 12.0.3
+ rados_watch@Base 0.72.2
+ rados_watch_check@Base 0.93
+ rados_watch_flush@Base 0.93
+ rados_write@Base 0.72.2
+ rados_write_full@Base 0.72.2
+ rados_write_op_append@Base 0.79
+ rados_write_op_assert_exists@Base 0.79
+ rados_write_op_assert_version@Base 0.93
+ rados_write_op_cmpext@Base 12.0.3
+ rados_write_op_cmpxattr@Base 0.79
+ rados_write_op_create@Base 0.79
+ rados_write_op_exec@Base 0.79
+ rados_write_op_omap_clear@Base 0.79
+ rados_write_op_omap_cmp2@Base 13.2.0
+ rados_write_op_omap_cmp@Base 0.79
+ rados_write_op_omap_rm_keys2@Base 13.2.0
+ rados_write_op_omap_rm_keys@Base 0.79
+ rados_write_op_omap_set2@Base 13.2.0
+ rados_write_op_omap_set@Base 0.79
+ rados_write_op_operate2@Base 10.1.0
+ rados_write_op_operate@Base 0.79
+ rados_write_op_remove@Base 0.79
+ rados_write_op_rmxattr@Base 0.79
+ rados_write_op_set_alloc_hint2@Base 12.0.3
+ rados_write_op_set_alloc_hint@Base 0.79
+ rados_write_op_set_flags@Base 0.79
+ rados_write_op_setxattr@Base 0.79
+ rados_write_op_truncate@Base 0.79
+ rados_write_op_write@Base 0.79
+ rados_write_op_write_full@Base 0.79
+ rados_write_op_writesame@Base 12.0.3
+ rados_write_op_zero@Base 0.79
+ rados_writesame@Base 12.0.3
diff --git a/libradospp-dev.install b/libradospp-dev.install
new file mode 100644 (file)
index 0000000..749cdd7
--- /dev/null
@@ -0,0 +1,8 @@
+usr/include/rados/buffer.h
+usr/include/rados/buffer_fwd.h
+usr/include/rados/crc32c.h
+usr/include/rados/inline_memory.h
+usr/include/rados/librados.hpp
+usr/include/rados/librados_fwd.hpp
+usr/include/rados/page.h
+usr/include/rados/rados_types.hpp
diff --git a/libradosstriper-dev.install b/libradosstriper-dev.install
new file mode 100644 (file)
index 0000000..4d6d7b5
--- /dev/null
@@ -0,0 +1,3 @@
+usr/include/radosstriper/libradosstriper.h
+usr/include/radosstriper/libradosstriper.hpp
+usr/lib/*/libradosstriper.so
diff --git a/libradosstriper1.install b/libradosstriper1.install
new file mode 100644 (file)
index 0000000..742549b
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libradosstriper.so.*
diff --git a/libradosstriper1.symbols b/libradosstriper1.symbols
new file mode 100644 (file)
index 0000000..7fc1e36
--- /dev/null
@@ -0,0 +1,39 @@
+libradosstriper.so.1 libradosstriper1 #MINVER#
+ (regex|c++)"^_.*" 0.87
+ default_file_layout@Base 10.1.0
+ rados_striper_aio_append@Base 0.87
+ rados_striper_aio_flush@Base 0.87
+ rados_striper_aio_read@Base 0.87
+ rados_striper_aio_remove@Base 12.0.3
+ rados_striper_aio_stat@Base 12.0.3
+ rados_striper_aio_write@Base 0.87
+ rados_striper_aio_write_full@Base 0.87
+ rados_striper_append@Base 0.87
+ rados_striper_create@Base 0.87
+ rados_striper_destroy@Base 0.87
+ rados_striper_getxattr@Base 0.87
+ rados_striper_getxattrs@Base 0.87
+ rados_striper_getxattrs_end@Base 0.87
+ rados_striper_getxattrs_next@Base 0.87
+ rados_striper_multi_aio_create_completion@Base 0.87
+ rados_striper_multi_aio_get_return_value@Base 0.87
+ rados_striper_multi_aio_is_complete@Base 0.87
+ rados_striper_multi_aio_is_complete_and_cb@Base 0.87
+ rados_striper_multi_aio_is_safe@Base 0.87
+ rados_striper_multi_aio_is_safe_and_cb@Base 0.87
+ rados_striper_multi_aio_release@Base 0.87
+ rados_striper_multi_aio_wait_for_complete@Base 0.87
+ rados_striper_multi_aio_wait_for_complete_and_cb@Base 0.87
+ rados_striper_multi_aio_wait_for_safe@Base 0.87
+ rados_striper_multi_aio_wait_for_safe_and_cb@Base 0.87
+ rados_striper_read@Base 0.87
+ rados_striper_remove@Base 0.87
+ rados_striper_rmxattr@Base 0.87
+ rados_striper_set_object_layout_object_size@Base 0.87
+ rados_striper_set_object_layout_stripe_count@Base 0.87
+ rados_striper_set_object_layout_stripe_unit@Base 0.87
+ rados_striper_setxattr@Base 0.87
+ rados_striper_stat@Base 0.87
+ rados_striper_trunc@Base 0.87
+ rados_striper_write@Base 0.87
+ rados_striper_write_full@Base 0.87
diff --git a/librbd-dev.install b/librbd-dev.install
new file mode 100644 (file)
index 0000000..c6b455e
--- /dev/null
@@ -0,0 +1,4 @@
+usr/include/rbd/features.h
+usr/include/rbd/librbd.h
+usr/include/rbd/librbd.hpp
+usr/lib/*/librbd.so
diff --git a/librbd1.install b/librbd1.install
new file mode 100644 (file)
index 0000000..decadbc
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/librbd.so.*
diff --git a/librbd1.symbols b/librbd1.symbols
new file mode 100644 (file)
index 0000000..2e2f873
--- /dev/null
@@ -0,0 +1,242 @@
+librbd.so.1 librbd1 #MINVER#
+ (regex|c++)"^_.*" 0.87
+ rbd_aio_close@Base 10.1.0
+ rbd_aio_compare_and_write@Base 12.1.4
+ rbd_aio_create_completion@Base 0.72.2
+ rbd_aio_discard@Base 0.72.2
+ rbd_aio_flush@Base 0.72.2
+ rbd_aio_get_arg@Base 10.1.0
+ rbd_aio_get_return_value@Base 0.72.2
+ rbd_aio_is_complete@Base 0.72.2
+ rbd_aio_mirror_image_demote@Base 12.0.3
+ rbd_aio_mirror_image_get_info@Base 12.0.3
+ rbd_aio_mirror_image_get_status@Base 12.0.3
+ rbd_aio_mirror_image_promote@Base 12.0.3
+ rbd_aio_open@Base 10.1.0
+ rbd_aio_open_by_id@Base 12.0.3
+ rbd_aio_open_by_id_read_only@Base 12.0.3
+ rbd_aio_open_read_only@Base 10.1.0
+ rbd_aio_read2@Base 0.93
+ rbd_aio_read@Base 0.72.2
+ rbd_aio_readv@Base 12.0.3
+ rbd_aio_release@Base 0.72.2
+ rbd_aio_wait_for_complete@Base 0.72.2
+ rbd_aio_write2@Base 0.93
+ rbd_aio_write@Base 0.72.2
+ rbd_aio_write_zeroes@Base 14.2.15
+ rbd_aio_writesame@Base 12.0.3
+ rbd_aio_writev@Base 12.0.3
+ rbd_break_lock@Base 0.72.2
+ rbd_clone2@Base 0.72.2
+ rbd_clone3@Base 10.1.0
+ rbd_clone@Base 0.72.2
+ rbd_close@Base 0.72.2
+ rbd_compare_and_write@Base 12.1.4
+ rbd_config_image_list@Base 14.2.0
+ rbd_config_image_list_cleanup@Base 14.2.0
+ rbd_config_pool_list@Base 14.2.0
+ rbd_config_pool_list_cleanup@Base 14.2.0
+ rbd_copy2@Base 0.72.2
+ rbd_copy3@Base 10.1.0
+ rbd_copy4@Base 12.0.3
+ rbd_copy@Base 0.72.2
+ rbd_copy_with_progress2@Base 0.72.2
+ rbd_copy_with_progress3@Base 12.0.3
+ rbd_copy_with_progress4@Base 12.0.3
+ rbd_copy_with_progress@Base 0.72.2
+ rbd_create2@Base 0.72.2
+ rbd_create3@Base 0.72.2
+ rbd_create4@Base 10.1.0
+ rbd_create@Base 0.72.2
+ rbd_deep_copy@Base 13.2.0
+ rbd_deep_copy_with_progress@Base 13.2.0
+ rbd_diff_iterate2@Base 9.2.0
+ rbd_diff_iterate@Base 0.72.2
+ rbd_discard@Base 0.72.2
+ rbd_flatten@Base 0.72.2
+ rbd_flatten_with_progress@Base 12.0.3
+ rbd_flush@Base 0.72.2
+ rbd_get_access_timestamp@Base 14.2.0
+ rbd_get_block_name_prefix@Base 12.0.3
+ rbd_get_create_timestamp@Base 12.1.4
+ rbd_get_data_pool_id@Base 12.0.3
+ rbd_get_features@Base 0.72.2
+ rbd_get_flags@Base 0.93
+ rbd_get_group@Base 13.2.0
+ rbd_get_id@Base 12.0.3
+ rbd_get_modify_timestamp@Base 14.2.0
+ rbd_get_name@Base 13.2.0
+ rbd_get_old_format@Base 0.72.2
+ rbd_get_op_features@Base 13.2.0
+ rbd_get_overlap@Base 0.72.2
+ rbd_get_parent@Base 14.2.0
+ rbd_get_parent_info2@Base 12.0.3
+ rbd_get_parent_info@Base 0.72.2
+ rbd_get_size@Base 0.72.2
+ rbd_get_stripe_count@Base 0.72.2
+ rbd_get_stripe_unit@Base 0.72.2
+ rbd_group_create@Base 13.2.0
+ rbd_group_image_add@Base 13.2.0
+ rbd_group_image_list@Base 13.2.0
+ rbd_group_image_list_cleanup@Base 13.2.0
+ rbd_group_image_remove@Base 13.2.0
+ rbd_group_image_remove_by_id@Base 13.2.0
+ rbd_group_info_cleanup@Base 13.2.0
+ rbd_group_list@Base 13.2.0
+ rbd_group_remove@Base 13.2.0
+ rbd_group_rename@Base 13.2.0
+ rbd_group_snap_create@Base 13.2.0
+ rbd_group_snap_list@Base 13.2.0
+ rbd_group_snap_list_cleanup@Base 13.2.0
+ rbd_group_snap_remove@Base 13.2.0
+ rbd_group_snap_rename@Base 13.2.0
+ rbd_group_snap_rollback@Base 14.2.0
+ rbd_group_snap_rollback_with_progress@Base 14.2.0
+ rbd_image_options_clear@Base 10.1.0
+ rbd_image_options_create@Base 10.1.0
+ rbd_image_options_destroy@Base 10.1.0
+ rbd_image_options_get_string@Base 10.1.0
+ rbd_image_options_get_uint64@Base 10.1.0
+ rbd_image_options_is_empty@Base 10.1.0
+ rbd_image_options_is_set@Base 12.0.3
+ rbd_image_options_set_string@Base 10.1.0
+ rbd_image_options_set_uint64@Base 10.1.0
+ rbd_image_options_unset@Base 10.1.0
+ rbd_image_spec_cleanup@Base 14.2.0
+ rbd_image_spec_list_cleanup@Base 14.2.0
+ rbd_invalidate_cache@Base 0.80.5-2~
+ rbd_is_exclusive_lock_owner@Base 0.93
+ rbd_linked_image_spec_cleanup@Base 14.2.0
+ rbd_linked_image_spec_list_cleanup@Base 14.2.0
+ rbd_list2@Base 14.2.0
+ rbd_list@Base 0.72.2
+ rbd_list_child_cleanup@Base 13.2.0
+ rbd_list_children2@Base 13.2.0
+ rbd_list_children3@Base 14.2.0
+ rbd_list_children@Base 0.72.2
+ rbd_list_children_cleanup@Base 13.2.0
+ rbd_list_descendants@Base 14.2.0
+ rbd_list_lockers@Base 0.72.2
+ rbd_lock_acquire@Base 12.0.3
+ rbd_lock_break@Base 12.0.3
+ rbd_lock_exclusive@Base 0.72.2
+ rbd_lock_get_owners@Base 12.0.3
+ rbd_lock_get_owners_cleanup@Base 12.0.3
+ rbd_lock_release@Base 12.0.3
+ rbd_lock_shared@Base 0.72.2
+ rbd_metadata_get@Base 9.2.0
+ rbd_metadata_list@Base 9.2.0
+ rbd_metadata_remove@Base 9.2.0
+ rbd_metadata_set@Base 9.2.0
+ rbd_migration_abort@Base 14.2.0
+ rbd_migration_abort_with_progress@Base 14.2.0
+ rbd_migration_commit@Base 14.2.0
+ rbd_migration_commit_with_progress@Base 14.2.0
+ rbd_migration_execute@Base 14.2.0
+ rbd_migration_execute_with_progress@Base 14.2.0
+ rbd_migration_prepare@Base 14.2.0
+ rbd_migration_status@Base 14.2.0
+ rbd_migration_status_cleanup@Base 14.2.0
+ rbd_mirror_image_demote@Base 10.1.0
+ rbd_mirror_image_disable@Base 10.1.0
+ rbd_mirror_image_enable@Base 10.1.0
+ rbd_mirror_image_get_info@Base 10.1.0
+ rbd_mirror_image_get_instance_id@Base 14.2.0
+ rbd_mirror_image_get_status@Base 12.0.3
+ rbd_mirror_image_instance_id_list@Base 14.2.0
+ rbd_mirror_image_instance_id_list_cleanup@Base 14.2.0
+ rbd_mirror_image_promote@Base 10.1.0
+ rbd_mirror_image_resync@Base 10.1.0
+ rbd_mirror_image_status_list@Base 12.0.3
+ rbd_mirror_image_status_list_cleanup@Base 12.0.3
+ rbd_mirror_image_status_summary@Base 12.0.3
+ rbd_mirror_mode_get@Base 10.1.0
+ rbd_mirror_mode_set@Base 10.1.0
+ rbd_mirror_peer_add@Base 10.1.0
+ rbd_mirror_peer_bootstrap_create@Base 14.2.5
+ rbd_mirror_peer_bootstrap_import@Base 14.2.5
+ rbd_mirror_peer_get_attributes@Base 14.2.0
+ rbd_mirror_peer_list@Base 10.1.0
+ rbd_mirror_peer_list_cleanup@Base 10.1.0
+ rbd_mirror_peer_remove@Base 10.1.0
+ rbd_mirror_peer_set_attributes@Base 14.2.0
+ rbd_mirror_peer_set_client@Base 10.1.0
+ rbd_mirror_peer_set_cluster@Base 10.1.0
+ rbd_mirror_site_name_get@Base 14.2.5
+ rbd_mirror_site_name_set@Base 14.2.5
+ rbd_namespace_create@Base 14.2.0
+ rbd_namespace_exists@Base 14.2.0
+ rbd_namespace_list@Base 14.2.0
+ rbd_namespace_remove@Base 14.2.0
+ rbd_open@Base 0.72.2
+ rbd_open_by_id@Base 12.0.3
+ rbd_open_by_id_read_only@Base 12.0.3
+ rbd_open_read_only@Base 0.72.2
+ rbd_poll_io_events@Base 10.1.0
+ rbd_pool_init@Base 14.2.0
+ rbd_pool_metadata_get@Base 14.2.0
+ rbd_pool_metadata_list@Base 14.2.0
+ rbd_pool_metadata_remove@Base 14.2.0
+ rbd_pool_metadata_set@Base 14.2.0
+ rbd_pool_stats_create@Base 14.2.0
+ rbd_pool_stats_destroy@Base 14.2.0
+ rbd_pool_stats_get@Base 14.2.0
+ rbd_pool_stats_option_add_uint64@Base 14.2.0
+ rbd_read2@Base 0.93
+ rbd_read@Base 0.72.2
+ rbd_read_iterate2@Base 0.72.2
+ rbd_read_iterate@Base 0.72.2
+ rbd_rebuild_object_map@Base 9.2.0
+ rbd_remove@Base 0.72.2
+ rbd_remove_with_progress@Base 0.72.2
+ rbd_rename@Base 0.72.2
+ rbd_resize2@Base 12.0.3
+ rbd_resize@Base 0.72.2
+ rbd_resize_with_progress@Base 0.72.2
+ rbd_set_image_notification@Base 10.1.0
+ rbd_snap_create@Base 0.72.2
+ rbd_snap_get_group_namespace@Base 13.2.0
+ rbd_snap_get_limit@Base 12.0.3
+ rbd_snap_get_namespace_type@Base 13.2.0
+ rbd_snap_get_timestamp@Base 12.0.3
+ rbd_snap_get_trash_namespace@Base 14.2.0
+ rbd_snap_group_namespace_cleanup@Base 13.2.0
+ rbd_snap_is_protected@Base 0.72.2
+ rbd_snap_list@Base 0.72.2
+ rbd_snap_list_end@Base 0.72.2
+ rbd_snap_protect@Base 0.72.2
+ rbd_snap_remove2@Base 12.0.3
+ rbd_snap_remove@Base 0.72.2
+ rbd_snap_remove_by_id@Base 14.2.0
+ rbd_snap_rename@Base 10.1.0
+ rbd_snap_rollback@Base 0.72.2
+ rbd_snap_rollback_with_progress@Base 0.72.2
+ rbd_snap_set@Base 0.72.2
+ rbd_snap_set_by_id@Base 13.2.0
+ rbd_snap_set_limit@Base 12.0.3
+ rbd_snap_spec_cleanup@Base 14.2.0
+ rbd_snap_unprotect@Base 0.72.2
+ rbd_sparsify@Base 14.2.0
+ rbd_sparsify_with_progress@Base 14.2.0
+ rbd_stat@Base 0.72.2
+ rbd_trash_get@Base 12.0.3
+ rbd_trash_get_cleanup@Base 12.0.3
+ rbd_trash_list@Base 12.0.3
+ rbd_trash_list_cleanup@Base 12.0.3
+ rbd_trash_move@Base 12.0.3
+ rbd_trash_purge@Base 14.2.0
+ rbd_trash_purge_with_progress@Base 14.2.0
+ rbd_trash_remove@Base 12.0.3
+ rbd_trash_remove_with_progress@Base 12.0.3
+ rbd_trash_restore@Base 12.0.3
+ rbd_unlock@Base 0.72.2
+ rbd_update_features@Base 9.2.0
+ rbd_update_unwatch@Base 12.0.3
+ rbd_update_watch@Base 12.0.3
+ rbd_version@Base 0.72.2
+ rbd_watchers_list@Base 13.2.0
+ rbd_watchers_list_cleanup@Base 13.2.0
+ rbd_write2@Base 0.93
+ rbd_write@Base 0.72.2
+ rbd_write_zeroes@Base 14.2.15
+ rbd_writesame@Base 12.0.3
diff --git a/librgw-dev.install b/librgw-dev.install
new file mode 100644 (file)
index 0000000..1fff6e4
--- /dev/null
@@ -0,0 +1,5 @@
+usr/include/rados/librgw.h
+usr/include/rados/librgw_admin_user.h
+usr/include/rados/rgw_file.h
+usr/lib/*/librgw.so
+usr/lib/*/librgw_admin_user.so
diff --git a/librgw2.install b/librgw2.install
new file mode 100644 (file)
index 0000000..c6fd3ab
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/*/librgw.so.*
+usr/lib/*/librgw_admin_user.so.*
diff --git a/man/ceph-crush-location.1 b/man/ceph-crush-location.1
new file mode 100644 (file)
index 0000000..d05d85c
--- /dev/null
@@ -0,0 +1,24 @@
+.TH ceph-crush-location "1" "April 2014" "ceph-crush-location" "User Commands"
+.SH NAME
+ceph-crush-location \- get CRUSH location
+.SH DESCRIPTION
+Generate a CRUSH location for the given entity
+
+The CRUSH location consists of a list of key=value pairs, separated
+by spaces, all on a single line.  This describes where in CRUSH
+hierarhcy this entity should be placed.
+
+.SH OPTIONS
+.TP 4
+\fB\-\-cluster\fR <clustername>
+name of the cluster (see /etc/ceph/$cluster.conf)
+.TP 4
+\fB\-\-type\fR <osd|mds|client>
+daemon/entity type
+.TP 4
+\fB\-\-id\fR <id>
+id (osd number, mds name, client name)
+
+.SH SEE ALSO
+.TP
+\fBceph-conf\fP(8)
diff --git a/man/mount.fuse.ceph.8 b/man/mount.fuse.ceph.8
new file mode 100644 (file)
index 0000000..7189365
--- /dev/null
@@ -0,0 +1,30 @@
+.TH mount.fuse.ceph "8" "March 2014" "ceph-fuse" "User Commands"
+.SH NAME
+mount.fuse.ceph \- wrapper around ceph-fuse
+.SH DESCRIPTION
+Helper to mount ceph-fuse from /etc/fstab. To use, add an entry like:
+
+.nf
+#  DEVICE                                              PATH         TYPE      OPTIONS
+  mount.fuse.ceph#conf=/etc/ceph/ceph.conf,id=admin   /mnt/ceph     fuse     _netdev,noatime,allow_other     0    0
+  mount.fuse.ceph#conf=/etc/ceph/foo.conf,id=myuser   /mnt/ceph2    fuse     _netdev,noatime,allow_other     0    0
+.fi
+
+where the device field is a comma-separated list of options to pass on
+the command line.  The examples above, for example, specify that
+ceph-fuse will authenticated as client.admin and client.myuser
+(respectively), and the second example also sets the "conf" option to
+"/etc/ceph/foo.conf" via the ceph-fuse command line. Any valid
+ceph-fuse option can be passed in this way.
+
+.SH OPTIONS
+.TP 4
+\fB\-\-conf\fR
+path to ceph cponfiguration file, usually "/etc/ceph/ceph.conf"
+.TP 4
+\fB\-\-id\fR
+user name
+
+.SH SEE ALSO
+.TP
+\fBceph-fuse\fP(8)
diff --git a/missing-sources/bootstrap.js b/missing-sources/bootstrap.js
new file mode 100644 (file)
index 0000000..8a2e99a
--- /dev/null
@@ -0,0 +1,2377 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under the MIT license
+ */
+
+if (typeof jQuery === 'undefined') {
+  throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+  'use strict';
+  var version = $.fn.jquery.split(' ')[0].split('.')
+  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
+    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
+  }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.7
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      WebkitTransition : 'webkitTransitionEnd',
+      MozTransition    : 'transitionend',
+      OTransition      : 'oTransitionEnd otransitionend',
+      transition       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+
+    return false // explicit for ie8 (  ._.)
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false
+    var $el = this
+    $(this).one('bsTransitionEnd', function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+
+    if (!$.support.transition) return
+
+    $.event.special.bsTransitionEnd = {
+      bindType: $.support.transition.end,
+      delegateType: $.support.transition.end,
+      handle: function (e) {
+        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+      }
+    }
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.7
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.VERSION = '3.3.7'
+
+  Alert.TRANSITION_DURATION = 150
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector === '#' ? [] : selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.closest('.alert')
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      // detach from parent, fire event then clean up data
+      $parent.detach().trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one('bsTransitionEnd', removeElement)
+        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.alert
+
+  $.fn.alert             = Plugin
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.7
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element  = $(element)
+    this.options   = $.extend({}, Button.DEFAULTS, options)
+    this.isLoading = false
+  }
+
+  Button.VERSION  = '3.3.7'
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state += 'Text'
+
+    if (data.resetText == null) $el.data('resetText', $el[val]())
+
+    // push to event loop to allow forms to submit
+    setTimeout($.proxy(function () {
+      $el[val](data[state] == null ? this.options[state] : data[state])
+
+      if (state == 'loadingText') {
+        this.isLoading = true
+        $el.addClass(d).attr(d, d).prop(d, true)
+      } else if (this.isLoading) {
+        this.isLoading = false
+        $el.removeClass(d).removeAttr(d).prop(d, false)
+      }
+    }, this), 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var changed = true
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+      if ($input.prop('type') == 'radio') {
+        if ($input.prop('checked')) changed = false
+        $parent.find('.active').removeClass('active')
+        this.$element.addClass('active')
+      } else if ($input.prop('type') == 'checkbox') {
+        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
+        this.$element.toggleClass('active')
+      }
+      $input.prop('checked', this.$element.hasClass('active'))
+      if (changed) $input.trigger('change')
+    } else {
+      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+      this.$element.toggleClass('active')
+    }
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  var old = $.fn.button
+
+  $.fn.button             = Plugin
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document)
+    .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      var $btn = $(e.target).closest('.btn')
+      Plugin.call($btn, 'toggle')
+      if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
+        // Prevent double click on radios, and the double selections (so cancellation) on checkboxes
+        e.preventDefault()
+        // The target component still receive the focus
+        if ($btn.is('input,button')) $btn.trigger('focus')
+        else $btn.find('input:visible,button:visible').first().trigger('focus')
+      }
+    })
+    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+    })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.7
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      = null
+    this.sliding     = null
+    this.interval    = null
+    this.$active     = null
+    this.$items      = null
+
+    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+  }
+
+  Carousel.VERSION  = '3.3.7'
+
+  Carousel.TRANSITION_DURATION = 600
+
+  Carousel.DEFAULTS = {
+    interval: 5000,
+    pause: 'hover',
+    wrap: true,
+    keyboard: true
+  }
+
+  Carousel.prototype.keydown = function (e) {
+    if (/input|textarea/i.test(e.target.tagName)) return
+    switch (e.which) {
+      case 37: this.prev(); break
+      case 39: this.next(); break
+      default: return
+    }
+
+    e.preventDefault()
+  }
+
+  Carousel.prototype.cycle = function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getItemIndex = function (item) {
+    this.$items = item.parent().children('.item')
+    return this.$items.index(item || this.$active)
+  }
+
+  Carousel.prototype.getItemForDirection = function (direction, active) {
+    var activeIndex = this.getItemIndex(active)
+    var willWrap = (direction == 'prev' && activeIndex === 0)
+                || (direction == 'next' && activeIndex == (this.$items.length - 1))
+    if (willWrap && !this.options.wrap) return active
+    var delta = direction == 'prev' ? -1 : 1
+    var itemIndex = (activeIndex + delta) % this.$items.length
+    return this.$items.eq(itemIndex)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || this.getItemForDirection(type, $active)
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var that      = this
+
+    if ($next.hasClass('active')) return (this.sliding = false)
+
+    var relatedTarget = $next[0]
+    var slideEvent = $.Event('slide.bs.carousel', {
+      relatedTarget: relatedTarget,
+      direction: direction
+    })
+    this.$element.trigger(slideEvent)
+    if (slideEvent.isDefaultPrevented()) return
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+      $nextIndicator && $nextIndicator.addClass('active')
+    }
+
+    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one('bsTransitionEnd', function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () {
+            that.$element.trigger(slidEvent)
+          }, 0)
+        })
+        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+    } else {
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger(slidEvent)
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  var old = $.fn.carousel
+
+  $.fn.carousel             = Plugin
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  var clickHandler = function (e) {
+    var href
+    var $this   = $(this)
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+    if (!$target.hasClass('carousel')) return
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    Plugin.call($target, options)
+
+    if (slideIndex) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  }
+
+  $(document)
+    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      Plugin.call($carousel, $carousel.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.7
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+/* jshint latedef: false */
+
++function ($) {
+  'use strict';
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.$trigger      = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
+                           '[data-toggle="collapse"][data-target="#' + element.id + '"]')
+    this.transitioning = null
+
+    if (this.options.parent) {
+      this.$parent = this.getParent()
+    } else {
+      this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+    }
+
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.VERSION  = '3.3.7'
+
+  Collapse.TRANSITION_DURATION = 350
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var activesData
+    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+    if (actives && actives.length) {
+      activesData = actives.data('bs.collapse')
+      if (activesData && activesData.transitioning) return
+    }
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    if (actives && actives.length) {
+      Plugin.call(actives, 'hide')
+      activesData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')[dimension](0)
+      .attr('aria-expanded', true)
+
+    this.$trigger
+      .removeClass('collapsed')
+      .attr('aria-expanded', true)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse in')[dimension]('')
+      this.transitioning = 0
+      this.$element
+        .trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse in')
+      .attr('aria-expanded', false)
+
+    this.$trigger
+      .addClass('collapsed')
+      .attr('aria-expanded', false)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse')
+        .trigger('hidden.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+  Collapse.prototype.getParent = function () {
+    return $(this.options.parent)
+      .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+      .each($.proxy(function (i, element) {
+        var $element = $(element)
+        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+      }, this))
+      .end()
+  }
+
+  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+    var isOpen = $element.hasClass('in')
+
+    $element.attr('aria-expanded', isOpen)
+    $trigger
+      .toggleClass('collapsed', !isOpen)
+      .attr('aria-expanded', isOpen)
+  }
+
+  function getTargetFromTrigger($trigger) {
+    var href
+    var target = $trigger.attr('data-target')
+      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+    return $(target)
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.collapse
+
+  $.fn.collapse             = Plugin
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+    var $this   = $(this)
+
+    if (!$this.attr('data-target')) e.preventDefault()
+
+    var $target = getTargetFromTrigger($this)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+
+    Plugin.call($target, option)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.7
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle="dropdown"]'
+  var Dropdown = function (element) {
+    $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.VERSION = '3.3.7'
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+  function clearMenus(e) {
+    if (e && e.which === 3) return
+    $(backdrop).remove()
+    $(toggle).each(function () {
+      var $this         = $(this)
+      var $parent       = getParent($this)
+      var relatedTarget = { relatedTarget: this }
+
+      if (!$parent.hasClass('open')) return
+
+      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this.attr('aria-expanded', 'false')
+      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+    })
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we use a backdrop because click events don't delegate
+        $(document.createElement('div'))
+          .addClass('dropdown-backdrop')
+          .insertAfter($(this))
+          .on('click', clearMenus)
+      }
+
+      var relatedTarget = { relatedTarget: this }
+      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this
+        .trigger('focus')
+        .attr('aria-expanded', 'true')
+
+      $parent
+        .toggleClass('open')
+        .trigger($.Event('shown.bs.dropdown', relatedTarget))
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive && e.which != 27 || isActive && e.which == 27) {
+      if (e.which == 27) $parent.find(toggle).trigger('focus')
+      return $this.trigger('click')
+    }
+
+    var desc = ' li:not(.disabled):visible a'
+    var $items = $parent.find('.dropdown-menu' + desc)
+
+    if (!$items.length) return
+
+    var index = $items.index(e.target)
+
+    if (e.which == 38 && index > 0)                 index--         // up
+    if (e.which == 40 && index < $items.length - 1) index++         // down
+    if (!~index)                                    index = 0
+
+    $items.eq(index).trigger('focus')
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.dropdown')
+
+      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown             = Plugin
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.7
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options             = options
+    this.$body               = $(document.body)
+    this.$element            = $(element)
+    this.$dialog             = this.$element.find('.modal-dialog')
+    this.$backdrop           = null
+    this.isShown             = null
+    this.originalBodyPad     = null
+    this.scrollbarWidth      = 0
+    this.ignoreBackdropClick = false
+
+    if (this.options.remote) {
+      this.$element
+        .find('.modal-content')
+        .load(this.options.remote, $.proxy(function () {
+          this.$element.trigger('loaded.bs.modal')
+        }, this))
+    }
+  }
+
+  Modal.VERSION  = '3.3.7'
+
+  Modal.TRANSITION_DURATION = 300
+  Modal.BACKDROP_TRANSITION_DURATION = 150
+
+  Modal.DEFAULTS = {
+    backdrop: true,
+    keyboard: true,
+    show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this.isShown ? this.hide() : this.show(_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.checkScrollbar()
+    this.setScrollbar()
+    this.$body.addClass('modal-open')
+
+    this.escape()
+    this.resize()
+
+    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.$dialog.on('mousedown.dismiss.bs.modal', function () {
+      that.$element.one('mouseup.dismiss.bs.modal', function (e) {
+        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
+      })
+    })
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(that.$body) // don't move modals dom position
+      }
+
+      that.$element
+        .show()
+        .scrollTop(0)
+
+      that.adjustDialog()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element.addClass('in')
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$dialog // wait for modal to slide in
+          .one('bsTransitionEnd', function () {
+            that.$element.trigger('focus').trigger(e)
+          })
+          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+        that.$element.trigger('focus').trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+    this.resize()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .off('click.dismiss.bs.modal')
+      .off('mouseup.dismiss.bs.modal')
+
+    this.$dialog.off('mousedown.dismiss.bs.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (document !== e.target &&
+            this.$element[0] !== e.target &&
+            !this.$element.has(e.target).length) {
+          this.$element.trigger('focus')
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keydown.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.resize = function () {
+    if (this.isShown) {
+      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+    } else {
+      $(window).off('resize.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.$body.removeClass('modal-open')
+      that.resetAdjustments()
+      that.resetScrollbar()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $(document.createElement('div'))
+        .addClass('modal-backdrop ' + animate)
+        .appendTo(this.$body)
+
+      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+        if (this.ignoreBackdropClick) {
+          this.ignoreBackdropClick = false
+          return
+        }
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus()
+          : this.hide()
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one('bsTransitionEnd', callback)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      var callbackRemove = function () {
+        that.removeBackdrop()
+        callback && callback()
+      }
+      $.support.transition && this.$element.hasClass('fade') ?
+        this.$backdrop
+          .one('bsTransitionEnd', callbackRemove)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callbackRemove()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+  // these following methods are used to handle overflowing modals
+
+  Modal.prototype.handleUpdate = function () {
+    this.adjustDialog()
+  }
+
+  Modal.prototype.adjustDialog = function () {
+    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+    this.$element.css({
+      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+    })
+  }
+
+  Modal.prototype.resetAdjustments = function () {
+    this.$element.css({
+      paddingLeft: '',
+      paddingRight: ''
+    })
+  }
+
+  Modal.prototype.checkScrollbar = function () {
+    var fullWindowWidth = window.innerWidth
+    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
+      var documentElementRect = document.documentElement.getBoundingClientRect()
+      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
+    }
+    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
+    this.scrollbarWidth = this.measureScrollbar()
+  }
+
+  Modal.prototype.setScrollbar = function () {
+    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+    this.originalBodyPad = document.body.style.paddingRight || ''
+    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+  }
+
+  Modal.prototype.resetScrollbar = function () {
+    this.$body.css('padding-right', this.originalBodyPad)
+  }
+
+  Modal.prototype.measureScrollbar = function () { // thx walsh
+    var scrollDiv = document.createElement('div')
+    scrollDiv.className = 'modal-scrollbar-measure'
+    this.$body.append(scrollDiv)
+    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+    this.$body[0].removeChild(scrollDiv)
+    return scrollbarWidth
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  var old = $.fn.modal
+
+  $.fn.modal             = Plugin
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    if ($this.is('a')) e.preventDefault()
+
+    $target.one('show.bs.modal', function (showEvent) {
+      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+      $target.one('hidden.bs.modal', function () {
+        $this.is(':visible') && $this.trigger('focus')
+      })
+    })
+    Plugin.call($target, option, this)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.7
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       = null
+    this.options    = null
+    this.enabled    = null
+    this.timeout    = null
+    this.hoverState = null
+    this.$element   = null
+    this.inState    = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.VERSION  = '3.3.7'
+
+  Tooltip.TRANSITION_DURATION = 150
+
+  Tooltip.DEFAULTS = {
+    animation: true,
+    placement: 'top',
+    selector: false,
+    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+    trigger: 'hover focus',
+    title: '',
+    delay: 0,
+    html: false,
+    container: false,
+    viewport: {
+      selector: 'body',
+      padding: 0
+    }
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled   = true
+    this.type      = type
+    this.$element  = $(element)
+    this.options   = this.getOptions(options)
+    this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
+    this.inState   = { click: false, hover: false, focus: false }
+
+    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
+      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
+    }
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay,
+        hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
+    }
+
+    if (self.tip().hasClass('in') || self.hoverState == 'in') {
+      self.hoverState = 'in'
+      return
+    }
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.isInStateTrue = function () {
+    for (var key in this.inState) {
+      if (this.inState[key]) return true
+    }
+
+    return false
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
+    }
+
+    if (self.isInStateTrue()) return
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.' + this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+      if (e.isDefaultPrevented() || !inDom) return
+      var that = this
+
+      var $tip = this.tip()
+
+      var tipId = this.getUID(this.type)
+
+      this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+        .data('bs.' + this.type, this)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+      this.$element.trigger('inserted.bs.' + this.type)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var orgPlacement = placement
+        var viewportDim = this.getPosition(this.$viewport)
+
+        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
+                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
+                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
+                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+
+      var complete = function () {
+        var prevHoverState = that.hoverState
+        that.$element.trigger('shown.bs.' + that.type)
+        that.hoverState = null
+
+        if (prevHoverState == 'out') that.leave(that)
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        $tip
+          .one('bsTransitionEnd', complete)
+          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+        complete()
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function (offset, placement) {
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  += marginTop
+    offset.left += marginLeft
+
+    // $.fn.offset doesn't round pixel values
+    // so we use setOffset directly with our own function B-0
+    $.offset.setOffset($tip[0], $.extend({
+      using: function (props) {
+        $tip.css({
+          top: Math.round(props.top),
+          left: Math.round(props.left)
+        })
+      }
+    }, offset), 0)
+
+    $tip.addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      offset.top = offset.top + height - actualHeight
+    }
+
+    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+    if (delta.left) offset.left += delta.left
+    else offset.top += delta.top
+
+    var isVertical          = /top|bottom/.test(placement)
+    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+    $tip.offset(offset)
+    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+  }
+
+  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
+    this.arrow()
+      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+      .css(isVertical ? 'top' : 'left', '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function (callback) {
+    var that = this
+    var $tip = $(this.$tip)
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
+        that.$element
+          .removeAttr('aria-describedby')
+          .trigger('hidden.bs.' + that.type)
+      }
+      callback && callback()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && $tip.hasClass('fade') ?
+      $tip
+        .one('bsTransitionEnd', complete)
+        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+      complete()
+
+    this.hoverState = null
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function ($element) {
+    $element   = $element || this.$element
+
+    var el     = $element[0]
+    var isBody = el.tagName == 'BODY'
+
+    var elRect    = el.getBoundingClientRect()
+    if (elRect.width == null) {
+      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+    }
+    var isSvg = window.SVGElement && el instanceof window.SVGElement
+    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
+    // See https://github.com/twbs/bootstrap/issues/20280
+    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
+    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+    return $.extend({}, elRect, scroll, outerDims, elOffset)
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+  }
+
+  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+    var delta = { top: 0, left: 0 }
+    if (!this.$viewport) return delta
+
+    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+    var viewportDimensions = this.getPosition(this.$viewport)
+
+    if (/right|left/.test(placement)) {
+      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
+      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+      if (topEdgeOffset < viewportDimensions.top) { // top overflow
+        delta.top = viewportDimensions.top - topEdgeOffset
+      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+      }
+    } else {
+      var leftEdgeOffset  = pos.left - viewportPadding
+      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+        delta.left = viewportDimensions.left - leftEdgeOffset
+      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
+        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+      }
+    }
+
+    return delta
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
+  Tooltip.prototype.tip = function () {
+    if (!this.$tip) {
+      this.$tip = $(this.options.template)
+      if (this.$tip.length != 1) {
+        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
+      }
+    }
+    return this.$tip
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = this
+    if (e) {
+      self = $(e.currentTarget).data('bs.' + this.type)
+      if (!self) {
+        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+        $(e.currentTarget).data('bs.' + this.type, self)
+      }
+    }
+
+    if (e) {
+      self.inState.click = !self.inState.click
+      if (self.isInStateTrue()) self.enter(self)
+      else self.leave(self)
+    } else {
+      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+    }
+  }
+
+  Tooltip.prototype.destroy = function () {
+    var that = this
+    clearTimeout(this.timeout)
+    this.hide(function () {
+      that.$element.off('.' + that.type).removeData('bs.' + that.type)
+      if (that.$tip) {
+        that.$tip.detach()
+      }
+      that.$tip = null
+      that.$arrow = null
+      that.$viewport = null
+      that.$element = null
+    })
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip             = Plugin
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.7
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.VERSION  = '3.3.7'
+
+  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right',
+    trigger: 'click',
+    content: '',
+    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+    ](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.popover
+
+  $.fn.popover             = Plugin
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.7
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    this.$body          = $(document.body)
+    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target || '') + ' .nav li > a'
+    this.offsets        = []
+    this.targets        = []
+    this.activeTarget   = null
+    this.scrollHeight   = 0
+
+    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.VERSION  = '3.3.7'
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.getScrollHeight = function () {
+    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var that          = this
+    var offsetMethod  = 'offset'
+    var offsetBase    = 0
+
+    this.offsets      = []
+    this.targets      = []
+    this.scrollHeight = this.getScrollHeight()
+
+    if (!$.isWindow(this.$scrollElement[0])) {
+      offsetMethod = 'position'
+      offsetBase   = this.$scrollElement.scrollTop()
+    }
+
+    this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#./.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && $href.is(':visible')
+          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        that.offsets.push(this[0])
+        that.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.getScrollHeight()
+    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (this.scrollHeight != scrollHeight) {
+      this.refresh()
+    }
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+    }
+
+    if (activeTarget && scrollTop < offsets[0]) {
+      this.activeTarget = null
+      return this.clear()
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
+        && this.activate(targets[i])
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    this.clear()
+
+    var selector = this.selector +
+      '[data-target="' + target + '"],' +
+      this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length) {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate.bs.scrollspy')
+  }
+
+  ScrollSpy.prototype.clear = function () {
+    $(this.selector)
+      .parentsUntil(this.options.target, '.active')
+      .removeClass('active')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy             = Plugin
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load.bs.scrollspy.data-api', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      Plugin.call($spy, $spy.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.7
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    // jscs:disable requireDollarBeforejQueryAssignment
+    this.element = $(element)
+    // jscs:enable requireDollarBeforejQueryAssignment
+  }
+
+  Tab.VERSION = '3.3.7'
+
+  Tab.TRANSITION_DURATION = 150
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.data('target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var $previous = $ul.find('.active:last a')
+    var hideEvent = $.Event('hide.bs.tab', {
+      relatedTarget: $this[0]
+    })
+    var showEvent = $.Event('show.bs.tab', {
+      relatedTarget: $previous[0]
+    })
+
+    $previous.trigger(hideEvent)
+    $this.trigger(showEvent)
+
+    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.closest('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $previous.trigger({
+        type: 'hidden.bs.tab',
+        relatedTarget: $this[0]
+      })
+      $this.trigger({
+        type: 'shown.bs.tab',
+        relatedTarget: $previous[0]
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+          .removeClass('active')
+        .end()
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', false)
+
+      element
+        .addClass('active')
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', true)
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu').length) {
+        element
+          .closest('li.dropdown')
+            .addClass('active')
+          .end()
+          .find('[data-toggle="tab"]')
+            .attr('aria-expanded', true)
+      }
+
+      callback && callback()
+    }
+
+    $active.length && transition ?
+      $active
+        .one('bsTransitionEnd', next)
+        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tab
+
+  $.fn.tab             = Plugin
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  var clickHandler = function (e) {
+    e.preventDefault()
+    Plugin.call($(this), 'show')
+  }
+
+  $(document)
+    .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+    .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.7
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+
+    this.$target = $(this.options.target)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element     = $(element)
+    this.affixed      = null
+    this.unpin        = null
+    this.pinnedOffset = null
+
+    this.checkPosition()
+  }
+
+  Affix.VERSION  = '3.3.7'
+
+  Affix.RESET    = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0,
+    target: window
+  }
+
+  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+    var scrollTop    = this.$target.scrollTop()
+    var position     = this.$element.offset()
+    var targetHeight = this.$target.height()
+
+    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+    if (this.affixed == 'bottom') {
+      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+    }
+
+    var initializing   = this.affixed == null
+    var colliderTop    = initializing ? scrollTop : position.top
+    var colliderHeight = initializing ? targetHeight : height
+
+    if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+    return false
+  }
+
+  Affix.prototype.getPinnedOffset = function () {
+    if (this.pinnedOffset) return this.pinnedOffset
+    this.$element.removeClass(Affix.RESET).addClass('affix')
+    var scrollTop = this.$target.scrollTop()
+    var position  = this.$element.offset()
+    return (this.pinnedOffset = position.top - scrollTop)
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var height       = this.$element.height()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+    var scrollHeight = Math.max($(document).height(), $(document.body).height())
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+    if (this.affixed != affix) {
+      if (this.unpin != null) this.$element.css('top', '')
+
+      var affixType = 'affix' + (affix ? '-' + affix : '')
+      var e         = $.Event(affixType + '.bs.affix')
+
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      this.affixed = affix
+      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+      this.$element
+        .removeClass(Affix.RESET)
+        .addClass(affixType)
+        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+    }
+
+    if (affix == 'bottom') {
+      this.$element.offset({
+        top: scrollHeight - height - offsetBottom
+      })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.affix
+
+  $.fn.affix             = Plugin
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop    != null) data.offset.top    = data.offsetTop
+
+      Plugin.call($spy, data)
+    })
+  })
+
+}(jQuery);
diff --git a/missing-sources/two.js b/missing-sources/two.js
new file mode 100644 (file)
index 0000000..859d3b4
--- /dev/null
@@ -0,0 +1,10244 @@
+/**
+ * two.js
+ * a two-dimensional drawing api meant for modern browsers. It is renderer
+ * agnostic enabling the same api for rendering in multiple contexts: webgl,
+ * canvas2d, and svg.
+ *
+ * Copyright (c) 2012 - 2017 jonobr1 / http://jonobr1.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+this.Two = (function(previousTwo) {
+
+  var root = typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : null;
+  var toString = Object.prototype.toString;
+  var _ = {
+    // http://underscorejs.org/ • 1.8.3
+    _indexAmount: 0,
+    natural: {
+      slice: Array.prototype.slice,
+      indexOf: Array.prototype.indexOf,
+      keys: Object.keys,
+      bind: Function.prototype.bind,
+      create: Object.create
+    },
+    identity: function(value) {
+      return value;
+    },
+    isArguments: function(obj) {
+      return toString.call(obj) === '[object Arguments]';
+    },
+    isFunction: function(obj) {
+      return toString.call(obj) === '[object Function]';
+    },
+    isString: function(obj) {
+      return toString.call(obj) === '[object String]';
+    },
+    isNumber: function(obj) {
+      return toString.call(obj) === '[object Number]';
+    },
+    isDate: function(obj) {
+      return toString.call(obj) === '[object Date]';
+    },
+    isRegExp: function(obj) {
+      return toString.call(obj) === '[object RegExp]';
+    },
+    isError: function(obj) {
+      return toString.call(obj) === '[object Error]';
+    },
+    isFinite: function(obj) {
+      return isFinite(obj) && !isNaN(parseFloat(obj));
+    },
+    isNaN: function(obj) {
+      return _.isNumber(obj) && obj !== +obj;
+    },
+    isBoolean: function(obj) {
+      return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+    },
+    isNull: function(obj) {
+      return obj === null;
+    },
+    isUndefined: function(obj) {
+      return obj === void 0;
+    },
+    isEmpty: function(obj) {
+      if (obj == null) return true;
+      if (isArrayLike && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+      return _.keys(obj).length === 0;
+    },
+    isElement: function(obj) {
+      return !!(obj && obj.nodeType === 1);
+    },
+    isArray: Array.isArray || function(obj) {
+      return toString.call(obj) === '[object Array]';
+    },
+    isObject: function(obj) {
+      var type = typeof obj;
+      return type === 'function' || type === 'object' && !!obj;
+    },
+    toArray: function(obj) {
+      if (!obj) {
+        return [];
+      }
+      if (_.isArray(obj)) {
+        return slice.call(obj);
+      }
+      if (isArrayLike(obj)) {
+        return _.map(obj, _.identity);
+      }
+      return _.values(obj);
+    },
+    range: function(start, stop, step) {
+      if (stop == null) {
+        stop = start || 0;
+        start = 0;
+      }
+      step = step || 1;
+
+      var length = Math.max(Math.ceil((stop - start) / step), 0);
+      var range = Array(length);
+
+      for (var idx = 0; idx < length; idx++, start += step) {
+        range[idx] = start;
+      }
+
+      return range;
+    },
+    indexOf: function(list, item) {
+      if (!!_.natural.indexOf) {
+        return _.natural.indexOf.call(list, item);
+      }
+      for (var i = 0; i < list.length; i++) {
+        if (list[i] === item) {
+          return i;
+        }
+      }
+      return -1;
+    },
+    has: function(obj, key) {
+      return obj != null && hasOwnProperty.call(obj, key);
+    },
+    bind: function(func, ctx) {
+      var natural = _.natural.bind;
+      if (natural && func.bind === natural) {
+        return natural.apply(func, slice.call(arguments, 1));
+      }
+      var args = slice.call(arguments, 2);
+      return function() {
+        func.apply(ctx, args);
+      };
+    },
+    extend: function(base) {
+      var sources = slice.call(arguments, 1);
+      for (var i = 0; i < sources.length; i++) {
+        var obj = sources[i];
+        for (var k in obj) {
+          base[k] = obj[k];
+        }
+      }
+      return base;
+    },
+    defaults: function(base) {
+      var sources = slice.call(arguments, 1);
+      for (var i = 0; i < sources.length; i++) {
+        var obj = sources[i];
+        for (var k in obj) {
+          if (base[k] === void 0) {
+            base[k] = obj[k];
+          }
+        }
+      }
+      return base;
+    },
+    keys: function(obj) {
+      if (!_.isObject(obj)) {
+        return [];
+      }
+      if (_.natural.keys) {
+        return _.natural.keys(obj);
+      }
+      var keys = [];
+      for (var k in obj) {
+        if (_.has(obj, k)) {
+          keys.push(k);
+        }
+      }
+      return keys;
+    },
+    values: function(obj) {
+      var keys = _.keys(obj);
+      var values = [];
+      for (var i = 0; i < keys.length; i++) {
+        var k = keys[i];
+        values.push(obj[k]);
+      }
+      return values;
+    },
+    each: function(obj, iteratee, context) {
+      var ctx = context || this;
+      var keys = !isArrayLike(obj) && _.keys(obj);
+      var length = (keys || obj).length;
+      for (var i = 0; i < length; i++) {
+        var k = keys ? keys[i] : i;
+        iteratee.call(ctx, obj[k], k, obj);
+      }
+      return obj;
+    },
+    map: function(obj, iteratee, context) {
+      var ctx = context || this;
+      var keys = !isArrayLike(obj) && _.keys(obj);
+      var length = (keys || obj).length;
+      var result = [];
+      for (var i = 0; i < length; i++) {
+        var k = keys ? keys[i] : i;
+        result[i] = iteratee.call(ctx, obj[k], k, obj);
+      }
+      return result;
+    },
+    once: function(func) {
+      var init = false;
+      return function() {
+        if (!!init) {
+          return func;
+        }
+        init = true;
+        return func.apply(this, arguments);
+      }
+    },
+    after: function(times, func) {
+      return function() {
+        while (--times < 1) {
+          return func.apply(this, arguments);
+        }
+      }
+    },
+    uniqueId: function(prefix) {
+      var id = ++_._indexAmount + '';
+      return prefix ? prefix + id : id;
+    }
+  };
+
+  /**
+   * Constants
+   */
+
+  var sin = Math.sin,
+    cos = Math.cos,
+    atan2 = Math.atan2,
+    sqrt = Math.sqrt,
+    round = Math.round,
+    abs = Math.abs,
+    PI = Math.PI,
+    TWO_PI = PI * 2,
+    HALF_PI = PI / 2,
+    pow = Math.pow,
+    min = Math.min,
+    max = Math.max;
+
+  /**
+   * Localized variables
+   */
+
+  var count = 0;
+  var slice = _.natural.slice;
+  var perf = ((root.performance && root.performance.now) ? root.performance : Date);
+  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+  var getLength = function(obj) {
+    return obj == null ? void 0 : obj['length'];
+  };
+  var isArrayLike = function(collection) {
+    var length = getLength(collection);
+    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+  };
+
+  /**
+   * Cross browser dom events.
+   */
+  var dom = {
+
+    temp: (root.document ? root.document.createElement('div') : {}),
+
+    hasEventListeners: _.isFunction(root.addEventListener),
+
+    bind: function(elem, event, func, bool) {
+      if (this.hasEventListeners) {
+        elem.addEventListener(event, func, !!bool);
+      } else {
+        elem.attachEvent('on' + event, func);
+      }
+      return dom;
+    },
+
+    unbind: function(elem, event, func, bool) {
+      if (dom.hasEventListeners) {
+        elem.removeEventListeners(event, func, !!bool);
+      } else {
+        elem.detachEvent('on' + event, func);
+      }
+      return dom;
+    },
+
+    getRequestAnimationFrame: function() {
+
+      var lastTime = 0;
+      var vendors = ['ms', 'moz', 'webkit', 'o'];
+      var request = root.requestAnimationFrame, cancel;
+
+      if(!request) {
+        for (var i = 0; i < vendors.length; i++) {
+          request = root[vendors[i] + 'RequestAnimationFrame'] || request;
+          cancel = root[vendors[i] + 'CancelAnimationFrame']
+            || root[vendors[i] + 'CancelRequestAnimationFrame'] || cancel;
+        }
+
+        request = request || function(callback, element) {
+          var currTime = new Date().getTime();
+          var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+          var id = root.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
+          lastTime = currTime + timeToCall;
+          return id;
+        };
+        // cancel = cancel || function(id) {
+        //   clearTimeout(id);
+        // };
+      }
+
+      request.init = _.once(loop);
+
+      return request;
+
+    }
+
+  };
+
+  /**
+   * @class
+   */
+  var Two = root.Two = function(options) {
+
+    // Determine what Renderer to use and setup a scene.
+
+    var params = _.defaults(options || {}, {
+      fullscreen: false,
+      width: 640,
+      height: 480,
+      type: Two.Types.svg,
+      autostart: false
+    });
+
+    _.each(params, function(v, k) {
+      if (k === 'fullscreen' || k === 'autostart') {
+        return;
+      }
+      this[k] = v;
+    }, this);
+
+    // Specified domElement overrides type declaration only if the element does not support declared renderer type.
+    if (_.isElement(params.domElement)) {
+      var tagName = params.domElement.tagName.toLowerCase();
+      // TODO: Reconsider this if statement's logic.
+      if (!/^(CanvasRenderer-canvas|WebGLRenderer-canvas|SVGRenderer-svg)$/.test(this.type+'-'+tagName)) {
+        this.type = Two.Types[tagName];
+      }
+    }
+
+    this.renderer = new Two[this.type](this);
+    Two.Utils.setPlaying.call(this, params.autostart);
+    this.frameCount = 0;
+
+    if (params.fullscreen) {
+
+      var fitted = _.bind(fitToWindow, this);
+      _.extend(document.body.style, {
+        overflow: 'hidden',
+        margin: 0,
+        padding: 0,
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        position: 'fixed'
+      });
+      _.extend(this.renderer.domElement.style, {
+        display: 'block',
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        position: 'fixed'
+      });
+      dom.bind(root, 'resize', fitted);
+      fitted();
+
+
+    } else if (!_.isElement(params.domElement)) {
+
+      this.renderer.setSize(params.width, params.height, this.ratio);
+      this.width = params.width;
+      this.height = params.height;
+
+    }
+
+    this.scene = this.renderer.scene;
+
+    Two.Instances.push(this);
+    raf.init();
+
+  };
+
+  _.extend(Two, {
+
+    /**
+     * Access to root in other files.
+     */
+
+    root: root,
+
+    /**
+     * Primitive
+     */
+
+    Array: root.Float32Array || Array,
+
+    Types: {
+      webgl: 'WebGLRenderer',
+      svg: 'SVGRenderer',
+      canvas: 'CanvasRenderer'
+    },
+
+    Version: 'v0.7.0',
+
+    Identifier: 'two_',
+
+    Properties: {
+      hierarchy: 'hierarchy',
+      demotion: 'demotion'
+    },
+
+    Events: {
+      play: 'play',
+      pause: 'pause',
+      update: 'update',
+      render: 'render',
+      resize: 'resize',
+      change: 'change',
+      remove: 'remove',
+      insert: 'insert',
+      order: 'order',
+      load: 'load'
+    },
+
+    Commands: {
+      move: 'M',
+      line: 'L',
+      curve: 'C',
+      close: 'Z'
+    },
+
+    Resolution: 8,
+
+    Instances: [],
+
+    noConflict: function() {
+      root.Two = previousTwo;
+      return this;
+    },
+
+    uniqueId: function() {
+      var id = count;
+      count++;
+      return id;
+    },
+
+    Utils: _.extend(_, {
+
+      performance: perf,
+
+      defineProperty: function(property) {
+
+        var object = this;
+        var secret = '_' + property;
+        var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+        Object.defineProperty(object, property, {
+          enumerable: true,
+          get: function() {
+            return this[secret];
+          },
+          set: function(v) {
+            this[secret] = v;
+            this[flag] = true;
+          }
+        });
+
+      },
+
+      /**
+       * Release an arbitrary class' events from the two.js corpus and recurse
+       * through its children and or vertices.
+       */
+      release: function(obj) {
+
+        if (!_.isObject(obj)) {
+          return;
+        }
+
+        if (_.isFunction(obj.unbind)) {
+          obj.unbind();
+        }
+
+        if (obj.vertices) {
+          if (_.isFunction(obj.vertices.unbind)) {
+            obj.vertices.unbind();
+          }
+          _.each(obj.vertices, function(v) {
+            if (_.isFunction(v.unbind)) {
+              v.unbind();
+            }
+          });
+        }
+
+        if (obj.children) {
+          _.each(obj.children, function(obj) {
+            Two.Utils.release(obj);
+          });
+        }
+
+      },
+
+      xhr: function(path, callback) {
+
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', path);
+
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState === 4 && xhr.status === 200) {
+            callback(xhr.responseText);
+          }
+        };
+
+        xhr.send();
+        return xhr;
+
+      },
+
+      Curve: {
+
+        CollinearityEpsilon: pow(10, -30),
+
+        RecursionLimit: 16,
+
+        CuspLimit: 0,
+
+        Tolerance: {
+          distance: 0.25,
+          angle: 0,
+          epsilon: 0.01
+        },
+
+        // Lookup tables for abscissas and weights with values for n = 2 .. 16.
+        // As values are symmetric, only store half of them and adapt algorithm
+        // to factor in symmetry.
+        abscissas: [
+          [  0.5773502691896257645091488],
+          [0,0.7745966692414833770358531],
+          [  0.3399810435848562648026658,0.8611363115940525752239465],
+          [0,0.5384693101056830910363144,0.9061798459386639927976269],
+          [  0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
+          [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
+          [  0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
+          [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
+          [  0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
+          [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
+          [  0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
+          [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
+          [  0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
+          [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
+          [  0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
+        ],
+
+        weights: [
+          [1],
+          [0.8888888888888888888888889,0.5555555555555555555555556],
+          [0.6521451548625461426269361,0.3478548451374538573730639],
+          [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
+          [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
+          [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
+          [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
+          [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
+          [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
+          [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
+          [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
+          [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
+          [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
+          [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
+          [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
+        ]
+
+      },
+
+      /**
+       * Account for high dpi rendering.
+       * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
+       */
+
+      devicePixelRatio: root.devicePixelRatio || 1,
+
+      getBackingStoreRatio: function(ctx) {
+        return ctx.webkitBackingStorePixelRatio ||
+          ctx.mozBackingStorePixelRatio ||
+          ctx.msBackingStorePixelRatio ||
+          ctx.oBackingStorePixelRatio ||
+          ctx.backingStorePixelRatio || 1;
+      },
+
+      getRatio: function(ctx) {
+        return Two.Utils.devicePixelRatio / getBackingStoreRatio(ctx);
+      },
+
+      /**
+       * Properly defer play calling until after all objects
+       * have been updated with their newest styles.
+       */
+      setPlaying: function(b) {
+
+        this.playing = !!b;
+        return this;
+
+      },
+
+      /**
+       * Return the computed matrix of a nested object.
+       * TODO: Optimize traversal.
+       */
+      getComputedMatrix: function(object, matrix) {
+
+        matrix = (matrix && matrix.identity()) || new Two.Matrix();
+        var parent = object, matrices = [];
+
+        while (parent && parent._matrix) {
+          matrices.push(parent._matrix);
+          parent = parent.parent;
+        }
+
+        matrices.reverse();
+
+        _.each(matrices, function(m) {
+
+          var e = m.elements;
+          matrix.multiply(
+            e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]);
+
+        });
+
+        return matrix;
+
+      },
+
+      deltaTransformPoint: function(matrix, x, y) {
+
+        var dx = x * matrix.a + y * matrix.c + 0;
+        var dy = x * matrix.b + y * matrix.d + 0;
+
+        return new Two.Vector(dx, dy);
+
+      },
+
+      /**
+       * https://gist.github.com/2052247
+       */
+      decomposeMatrix: function(matrix) {
+
+        // calculate delta transform point
+        var px = Two.Utils.deltaTransformPoint(matrix, 0, 1);
+        var py = Two.Utils.deltaTransformPoint(matrix, 1, 0);
+
+        // calculate skew
+        var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
+        var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
+
+        return {
+            translateX: matrix.e,
+            translateY: matrix.f,
+            scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
+            scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
+            skewX: skewX,
+            skewY: skewY,
+            rotation: skewX // rotation is the same as skew x
+        };
+
+      },
+
+      /**
+       * Walk through item properties and pick the ones of interest.
+       * Will try to resolve styles applied via CSS
+       *
+       * TODO: Reverse calculate `Two.Gradient`s for fill / stroke
+       * of any given path.
+       */
+      applySvgAttributes: function(node, elem) {
+
+        var attributes = {}, styles = {}, i, key, value, attr;
+
+        // Not available in non browser environments
+        if (getComputedStyle) {
+          // Convert CSSStyleDeclaration to a normal object
+          var computedStyles = getComputedStyle(node);
+          i = computedStyles.length;
+
+          while (i--) {
+            key = computedStyles[i];
+            value = computedStyles[key];
+            // Gecko returns undefined for unset properties
+            // Webkit returns the default value
+            if (value !== undefined) {
+              styles[key] = value;
+            }
+          }
+        }
+
+        // Convert NodeMap to a normal object
+        i = node.attributes.length;
+        while (i--) {
+          attr = node.attributes[i];
+          attributes[attr.nodeName] = attr.value;
+        }
+
+        // Getting the correct opacity is a bit tricky, since SVG path elements don't
+        // support opacity as an attribute, but you can apply it via CSS.
+        // So we take the opacity and set (stroke/fill)-opacity to the same value.
+        if (!_.isUndefined(styles.opacity)) {
+          styles['stroke-opacity'] = styles.opacity;
+          styles['fill-opacity'] = styles.opacity;
+        }
+
+        // Merge attributes and applied styles (attributes take precedence)
+        _.extend(styles, attributes);
+
+        // Similarly visibility is influenced by the value of both display and visibility.
+        // Calculate a unified value here which defaults to `true`.
+        styles.visible = !(_.isUndefined(styles.display) && styles.display === 'none')
+          || (_.isUndefined(styles.visibility) && styles.visibility === 'hidden');
+
+        // Now iterate the whole thing
+        for (key in styles) {
+          value = styles[key];
+
+          switch (key) {
+            case 'transform':
+              // TODO: Check this out https://github.com/paperjs/paper.js/blob/master/src/svg/SVGImport.js#L313
+              if (value === 'none') break;
+              var m = node.getCTM ? node.getCTM() : null;
+
+              // Might happen when transform string is empty or not valid.
+              if (m === null) break;
+
+              // // Option 1: edit the underlying matrix and don't force an auto calc.
+              // var m = node.getCTM();
+              // elem._matrix.manual = true;
+              // elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
+
+              // Option 2: Decompose and infer Two.js related properties.
+              var transforms = Two.Utils.decomposeMatrix(node.getCTM());
+
+              elem.translation.set(transforms.translateX, transforms.translateY);
+              elem.rotation = transforms.rotation;
+              // Warning: Two.js elements only support uniform scalars...
+              elem.scale = transforms.scaleX;
+
+              var x = parseFloat((styles.x + '').replace('px'));
+              var y = parseFloat((styles.y + '').replace('px'));
+
+              // Override based on attributes.
+              if (x) {
+                elem.translation.x = x;
+              }
+
+              if (y) {
+                elem.translation.y = y;
+              }
+
+              break;
+            case 'visible':
+              elem.visible = value;
+              break;
+            case 'stroke-linecap':
+              elem.cap = value;
+              break;
+            case 'stroke-linejoin':
+              elem.join = value;
+              break;
+            case 'stroke-miterlimit':
+              elem.miter = value;
+              break;
+            case 'stroke-width':
+              elem.linewidth = parseFloat(value);
+              break;
+            case 'stroke-opacity':
+            case 'fill-opacity':
+            case 'opacity':
+              elem.opacity = parseFloat(value);
+              break;
+            case 'fill':
+            case 'stroke':
+              if (/url\(\#.*\)/i.test(value)) {
+                elem[key] = this.getById(
+                  value.replace(/url\(\#(.*)\)/i, '$1'));
+              } else {
+                elem[key] = (value === 'none') ? 'transparent' : value;
+              }
+              break;
+            case 'id':
+              elem.id = value;
+              break;
+            case 'class':
+              elem.classList = value.split(' ');
+              break;
+          }
+        }
+
+        return elem;
+
+      },
+
+      /**
+       * Read any number of SVG node types and create Two equivalents of them.
+       */
+      read: {
+
+        svg: function() {
+          return Two.Utils.read.g.apply(this, arguments);
+        },
+
+        g: function(node) {
+
+          var group = new Two.Group();
+
+          // Switched up order to inherit more specific styles
+          Two.Utils.applySvgAttributes.call(this, node, group);
+
+          for (var i = 0, l = node.childNodes.length; i < l; i++) {
+            var n = node.childNodes[i];
+            var tag = n.nodeName;
+            if (!tag) return;
+
+            var tagName = tag.replace(/svg\:/ig, '').toLowerCase();
+
+            if (tagName in Two.Utils.read) {
+              var o = Two.Utils.read[tagName].call(group, n);
+              group.add(o);
+            }
+          }
+
+          return group;
+
+        },
+
+        polygon: function(node, open) {
+
+          var points = node.getAttribute('points');
+
+          var verts = [];
+          points.replace(/(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g, function(match, p1, p2) {
+            verts.push(new Two.Anchor(parseFloat(p1), parseFloat(p2)));
+          });
+
+          var poly = new Two.Path(verts, !open).noStroke();
+          poly.fill = 'black';
+
+          return Two.Utils.applySvgAttributes.call(this, node, poly);
+
+        },
+
+        polyline: function(node) {
+          return Two.Utils.read.polygon.call(this, node, true);
+        },
+
+        path: function(node) {
+
+          var path = node.getAttribute('d');
+
+          // Create a Two.Path from the paths.
+
+          var coord = new Two.Anchor();
+          var control, coords;
+          var closed = false, relative = false;
+          var commands = path.match(/[a-df-z][^a-df-z]*/ig);
+          var last = commands.length - 1;
+
+          // Split up polybeziers
+
+          _.each(commands.slice(0), function(command, i) {
+
+            var type = command[0];
+            var lower = type.toLowerCase();
+            var items = command.slice(1).trim().split(/[\s,]+|(?=\s?[+\-])/);
+            var pre, post, result = [], bin;
+
+            if (i <= 0) {
+              commands = [];
+            }
+
+            switch (lower) {
+              case 'h':
+              case 'v':
+                if (items.length > 1) {
+                  bin = 1;
+                }
+                break;
+              case 'm':
+              case 'l':
+              case 't':
+                if (items.length > 2) {
+                  bin = 2;
+                }
+                break;
+              case 's':
+              case 'q':
+                if (items.length > 4) {
+                  bin = 4;
+                }
+                break;
+              case 'c':
+                if (items.length > 6) {
+                  bin = 6;
+                }
+                break;
+              case 'a':
+                // TODO: Handle Ellipses
+                break;
+            }
+
+            if (bin) {
+
+              for (var j = 0, l = items.length, times = 0; j < l; j+=bin) {
+
+                var ct = type;
+                if (times > 0) {
+
+                  switch (type) {
+                    case 'm':
+                      ct = 'l';
+                      break;
+                    case 'M':
+                      ct = 'L';
+                      break;
+                  }
+
+                }
+
+                result.push([ct].concat(items.slice(j, j + bin)).join(' '));
+                times++;
+
+              }
+
+              commands = Array.prototype.concat.apply(commands, result);
+
+            } else {
+
+              commands.push(command);
+
+            }
+
+          });
+
+          // Create the vertices for our Two.Path
+
+          var points = [];
+          _.each(commands, function(command, i) {
+
+            var result, x, y;
+            var type = command[0];
+            var lower = type.toLowerCase();
+
+            coords = command.slice(1).trim();
+            coords = coords.replace(/(-?\d+(?:\.\d*)?)[eE]([+\-]?\d+)/g, function(match, n1, n2) {
+              return parseFloat(n1) * pow(10, n2);
+            });
+            coords = coords.split(/[\s,]+|(?=\s?[+\-])/);
+            relative = type === lower;
+
+            var x1, y1, x2, y2, x3, y3, x4, y4, reflection;
+
+            switch (lower) {
+
+              case 'z':
+                if (i >= last) {
+                  closed = true;
+                } else {
+                  x = coord.x;
+                  y = coord.y;
+                  result = new Two.Anchor(
+                    x, y,
+                    undefined, undefined,
+                    undefined, undefined,
+                    Two.Commands.close
+                  );
+                }
+                break;
+
+              case 'm':
+              case 'l':
+
+                x = parseFloat(coords[0]);
+                y = parseFloat(coords[1]);
+
+                result = new Two.Anchor(
+                  x, y,
+                  undefined, undefined,
+                  undefined, undefined,
+                  lower === 'm' ? Two.Commands.move : Two.Commands.line
+                );
+
+                if (relative) {
+                  result.addSelf(coord);
+                }
+
+                // result.controls.left.copy(result);
+                // result.controls.right.copy(result);
+
+                coord = result;
+                break;
+
+              case 'h':
+              case 'v':
+
+                var a = lower === 'h' ? 'x' : 'y';
+                var b = a === 'x' ? 'y' : 'x';
+
+                result = new Two.Anchor(
+                  undefined, undefined,
+                  undefined, undefined,
+                  undefined, undefined,
+                  Two.Commands.line
+                );
+                result[a] = parseFloat(coords[0]);
+                result[b] = coord[b];
+
+                if (relative) {
+                  result[a] += coord[a];
+                }
+
+                // result.controls.left.copy(result);
+                // result.controls.right.copy(result);
+
+                coord = result;
+                break;
+
+              case 'c':
+              case 's':
+
+                x1 = coord.x;
+                y1 = coord.y;
+
+                if (!control) {
+                  control = new Two.Vector();//.copy(coord);
+                }
+
+                if (lower === 'c') {
+
+                  x2 = parseFloat(coords[0]);
+                  y2 = parseFloat(coords[1]);
+                  x3 = parseFloat(coords[2]);
+                  y3 = parseFloat(coords[3]);
+                  x4 = parseFloat(coords[4]);
+                  y4 = parseFloat(coords[5]);
+
+                } else {
+
+                  // Calculate reflection control point for proper x2, y2
+                  // inclusion.
+
+                  reflection = getReflection(coord, control, relative);
+
+                  x2 = reflection.x;
+                  y2 = reflection.y;
+                  x3 = parseFloat(coords[0]);
+                  y3 = parseFloat(coords[1]);
+                  x4 = parseFloat(coords[2]);
+                  y4 = parseFloat(coords[3]);
+
+                }
+
+                if (relative) {
+                  x2 += x1;
+                  y2 += y1;
+                  x3 += x1;
+                  y3 += y1;
+                  x4 += x1;
+                  y4 += y1;
+                }
+
+                if (!_.isObject(coord.controls)) {
+                  Two.Anchor.AppendCurveProperties(coord);
+                }
+
+                coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+                result = new Two.Anchor(
+                  x4, y4,
+                  x3 - x4, y3 - y4,
+                  undefined, undefined,
+                  Two.Commands.curve
+                );
+
+                coord = result;
+                control = result.controls.left;
+
+                break;
+
+              case 't':
+              case 'q':
+
+                x1 = coord.x;
+                y1 = coord.y;
+
+                if (!control) {
+                  control = new Two.Vector();//.copy(coord);
+                }
+
+                if (control.isZero()) {
+                  x2 = x1;
+                  y2 = y1;
+                } else {
+                  x2 = control.x;
+                  y1 = control.y;
+                }
+
+                if (lower === 'q') {
+
+                  x3 = parseFloat(coords[0]);
+                  y3 = parseFloat(coords[1]);
+                  x4 = parseFloat(coords[1]);
+                  y4 = parseFloat(coords[2]);
+
+                } else {
+
+                  reflection = getReflection(coord, control, relative);
+
+                  x3 = reflection.x;
+                  y3 = reflection.y;
+                  x4 = parseFloat(coords[0]);
+                  y4 = parseFloat(coords[1]);
+
+                }
+
+                if (relative) {
+                  x2 += x1;
+                  y2 += y1;
+                  x3 += x1;
+                  y3 += y1;
+                  x4 += x1;
+                  y4 += y1;
+                }
+
+                if (!_.isObject(coord.controls)) {
+                  Two.Anchor.AppendCurveProperties(coord);
+                }
+
+                coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+                result = new Two.Anchor(
+                  x4, y4,
+                  x3 - x4, y3 - y4,
+                  undefined, undefined,
+                  Two.Commands.curve
+                );
+
+                coord = result;
+                control = result.controls.left;
+
+                break;
+
+              case 'a':
+
+                // throw new Two.Utils.Error('not yet able to interpret Elliptical Arcs.');
+                x1 = coord.x;
+                y1 = coord.y;
+
+                var rx = parseFloat(coords[0]);
+                var ry = parseFloat(coords[1]);
+                var xAxisRotation = parseFloat(coords[2]) * Math.PI / 180;
+                var largeArcFlag = parseFloat(coords[3]);
+                var sweepFlag = parseFloat(coords[4]);
+
+                x4 = parseFloat(coords[5]);
+                y4 = parseFloat(coords[6]);
+
+                if (relative) {
+                  x4 += x1;
+                  y4 += y1;
+                }
+
+                // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+
+                // Calculate midpoint mx my
+                var mx = (x4 - x1) / 2;
+                var my = (y4 - y1) / 2;
+
+                // Calculate x1' y1' F.6.5.1
+                var _x = mx * Math.cos(xAxisRotation) + my * Math.sin(xAxisRotation);
+                var _y = - mx * Math.sin(xAxisRotation) + my * Math.cos(xAxisRotation);
+
+                var rx2 = rx * rx;
+                var ry2 = ry * ry;
+                var _x2 = _x * _x;
+                var _y2 = _y * _y;
+
+                // adjust radii
+                var l = _x2 / rx2 + _y2 / ry2;
+                if (l > 1) {
+                  rx *= Math.sqrt(l);
+                  ry *= Math.sqrt(l);
+                }
+
+                var amp = Math.sqrt((rx2 * ry2 - rx2 * _y2 - ry2 * _x2) / (rx2 * _y2 + ry2 * _x2));
+
+                if (_.isNaN(amp)) {
+                  amp = 0;
+                } else if (largeArcFlag != sweepFlag && amp > 0) {
+                  amp *= -1;
+                }
+
+                // Calculate cx' cy' F.6.5.2
+                var _cx = amp * rx * _y / ry;
+                var _cy = - amp * ry * _x / rx;
+
+                // Calculate cx cy F.6.5.3
+                var cx = _cx * Math.cos(xAxisRotation) - _cy * Math.sin(xAxisRotation) + (x1 + x4) / 2;
+                var cy = _cx * Math.sin(xAxisRotation) + _cy * Math.cos(xAxisRotation) + (y1 + y4) / 2;
+
+                // vector magnitude
+                var m = function(v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)); }
+                // ratio between two vectors
+                var r = function(u, v) { return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v)) }
+                // angle between two vectors
+                var a = function(u, v) { return (u[0] * v[1] < u[1] * v[0] ? - 1 : 1) * Math.acos(r(u,v)); }
+
+                // Calculate theta1 and delta theta F.6.5.4 + F.6.5.5
+                var t1 = a([1, 0], [(_x - _cx) / rx, (_y - _cy) / ry]);
+                var u = [(_x - _cx) / rx, (_y - _cy) / ry];
+                var v = [( - _x - _cx) / rx, ( - _y - _cy) / ry];
+                var dt = a(u, v);
+
+                if (r(u, v) <= -1) dt = Math.PI;
+                if (r(u, v) >= 1) dt = 0;
+
+                // F.6.5.6
+                if (largeArcFlag)  {
+                  dt = mod(dt, Math.PI * 2);
+                }
+
+                if (sweepFlag && dt > 0) {
+                  dt -= Math.PI * 2;
+                }
+
+                var length = Two.Resolution;
+
+                // Save a projection of our rotation and translation to apply
+                // to the set of points.
+                var projection = new Two.Matrix()
+                  .translate(cx, cy)
+                  .rotate(xAxisRotation);
+
+                // Create a resulting array of Two.Anchor's to export to the
+                // the path.
+                result = _.map(_.range(length), function(i) {
+                  var pct = 1 - (i / (length - 1));
+                  var theta = pct * dt + t1;
+                  var x = rx * Math.cos(theta);
+                  var y = ry * Math.sin(theta);
+                  var projected = projection.multiply(x, y, 1);
+                  return new Two.Anchor(projected.x, projected.y, false, false, false, false, Two.Commands.line);;
+                });
+
+                result.push(new Two.Anchor(x4, y4, false, false, false, false, Two.Commands.line));
+
+                coord = result[result.length - 1];
+                control = coord.controls.left;
+
+                break;
+
+            }
+
+            if (result) {
+              if (_.isArray(result)) {
+                points = points.concat(result);
+              } else {
+                points.push(result);
+              }
+            }
+
+          });
+
+          if (points.length <= 1) {
+            return;
+          }
+
+          var path = new Two.Path(points, closed, undefined, true).noStroke();
+          path.fill = 'black';
+
+          var rect = path.getBoundingClientRect(true);
+
+          // Center objects to stay consistent
+          // with the rest of the Two.js API.
+          rect.centroid = {
+            x: rect.left + rect.width / 2,
+            y: rect.top + rect.height / 2
+          };
+
+          _.each(path.vertices, function(v) {
+            v.subSelf(rect.centroid);
+          });
+
+          path.translation.addSelf(rect.centroid);
+
+          return Two.Utils.applySvgAttributes.call(this, node, path);
+
+        },
+
+        circle: function(node) {
+
+          var x = parseFloat(node.getAttribute('cx'));
+          var y = parseFloat(node.getAttribute('cy'));
+          var r = parseFloat(node.getAttribute('r'));
+
+          var circle = new Two.Circle(x, y, r).noStroke();
+          circle.fill = 'black';
+
+          return Two.Utils.applySvgAttributes.call(this, node, circle);
+
+        },
+
+        ellipse: function(node) {
+
+          var x = parseFloat(node.getAttribute('cx'));
+          var y = parseFloat(node.getAttribute('cy'));
+          var width = parseFloat(node.getAttribute('rx'));
+          var height = parseFloat(node.getAttribute('ry'));
+
+          var ellipse = new Two.Ellipse(x, y, width, height).noStroke();
+          ellipse.fill = 'black';
+
+          return Two.Utils.applySvgAttributes.call(this, node, ellipse);
+
+        },
+
+        rect: function(node) {
+
+          var x = parseFloat(node.getAttribute('x')) || 0;
+          var y = parseFloat(node.getAttribute('y')) || 0;
+          var width = parseFloat(node.getAttribute('width'));
+          var height = parseFloat(node.getAttribute('height'));
+
+          var w2 = width / 2;
+          var h2 = height / 2;
+
+          var rect = new Two.Rectangle(x + w2, y + h2, width, height)
+            .noStroke();
+          rect.fill = 'black';
+
+          return Two.Utils.applySvgAttributes.call(this, node, rect);
+
+        },
+
+        line: function(node) {
+
+          var x1 = parseFloat(node.getAttribute('x1'));
+          var y1 = parseFloat(node.getAttribute('y1'));
+          var x2 = parseFloat(node.getAttribute('x2'));
+          var y2 = parseFloat(node.getAttribute('y2'));
+
+          var line = new Two.Line(x1, y1, x2, y2).noFill();
+
+          return Two.Utils.applySvgAttributes.call(this, node, line);
+
+        },
+
+        lineargradient: function(node) {
+
+          var x1 = parseFloat(node.getAttribute('x1'));
+          var y1 = parseFloat(node.getAttribute('y1'));
+          var x2 = parseFloat(node.getAttribute('x2'));
+          var y2 = parseFloat(node.getAttribute('y2'));
+
+          var ox = (x2 + x1) / 2;
+          var oy = (y2 + y1) / 2;
+
+          var stops = [];
+          for (var i = 0; i < node.children.length; i++) {
+
+            var child = node.children[i];
+
+            var offset = parseFloat(child.getAttribute('offset'));
+            var color = child.getAttribute('stop-color');
+            var opacity = child.getAttribute('stop-opacity');
+            var style = child.getAttribute('style');
+
+            if (_.isNull(color)) {
+              var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
+              color = matches && matches.length > 1 ? matches[1] : undefined;
+            }
+
+            if (_.isNull(opacity)) {
+              var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
+              opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+            }
+
+            stops.push(new Two.Gradient.Stop(offset, color, opacity));
+
+          }
+
+          var gradient = new Two.LinearGradient(x1 - ox, y1 - oy, x2 - ox,
+            y2 - oy, stops);
+
+          return Two.Utils.applySvgAttributes.call(this, node, gradient);
+
+        },
+
+        radialgradient: function(node) {
+
+          var cx = parseFloat(node.getAttribute('cx')) || 0;
+          var cy = parseFloat(node.getAttribute('cy')) || 0;
+          var r = parseFloat(node.getAttribute('r'));
+
+          var fx = parseFloat(node.getAttribute('fx'));
+          var fy = parseFloat(node.getAttribute('fy'));
+
+          if (_.isNaN(fx)) {
+            fx = cx;
+          }
+
+          if (_.isNaN(fy)) {
+            fy = cy;
+          }
+
+          var ox = Math.abs(cx + fx) / 2;
+          var oy = Math.abs(cy + fy) / 2;
+
+          var stops = [];
+          for (var i = 0; i < node.children.length; i++) {
+
+            var child = node.children[i];
+
+            var offset = parseFloat(child.getAttribute('offset'));
+            var color = child.getAttribute('stop-color');
+            var opacity = child.getAttribute('stop-opacity');
+            var style = child.getAttribute('style');
+
+            if (_.isNull(color)) {
+              var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
+              color = matches && matches.length > 1 ? matches[1] : undefined;
+            }
+
+            if (_.isNull(opacity)) {
+              var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
+              opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+            }
+
+            stops.push(new Two.Gradient.Stop(offset, color, opacity));
+
+          }
+
+          var gradient = new Two.RadialGradient(cx - ox, cy - oy, r,
+            stops, fx - ox, fy - oy);
+
+          return Two.Utils.applySvgAttributes.call(this, node, gradient);
+
+        }
+
+      },
+
+      /**
+       * Given 2 points (a, b) and corresponding control point for each
+       * return an array of points that represent points plotted along
+       * the curve. Number points determined by limit.
+       */
+      subdivide: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+        limit = limit || Two.Utils.Curve.RecursionLimit;
+        var amount = limit + 1;
+
+        // TODO: Issue 73
+        // Don't recurse if the end points are identical
+        if (x1 === x4 && y1 === y4) {
+          return [new Two.Anchor(x4, y4)];
+        }
+
+        return _.map(_.range(0, amount), function(i) {
+
+          var t = i / amount;
+          var x = getPointOnCubicBezier(t, x1, x2, x3, x4);
+          var y = getPointOnCubicBezier(t, y1, y2, y3, y4);
+
+          return new Two.Anchor(x, y);
+
+        });
+
+      },
+
+      getPointOnCubicBezier: function(t, a, b, c, d) {
+        var k = 1 - t;
+        return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) +
+           (t * t * t * d);
+      },
+
+      /**
+       * Given 2 points (a, b) and corresponding control point for each
+       * return a float that represents the length of the curve using
+       * Gauss-Legendre algorithm. Limit iterations of calculation by `limit`.
+       */
+      getCurveLength: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+        // TODO: Better / fuzzier equality check
+        // Linear calculation
+        if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) {
+          var dx = x4 - x1;
+          var dy = y4 - y1;
+          return sqrt(dx * dx + dy * dy);
+        }
+
+        // Calculate the coefficients of a Bezier derivative.
+        var ax = 9 * (x2 - x3) + 3 * (x4 - x1),
+          bx = 6 * (x1 + x3) - 12 * x2,
+          cx = 3 * (x2 - x1),
+
+          ay = 9 * (y2 - y3) + 3 * (y4 - y1),
+          by = 6 * (y1 + y3) - 12 * y2,
+          cy = 3 * (y2 - y1);
+
+        var integrand = function(t) {
+          // Calculate quadratic equations of derivatives for x and y
+          var dx = (ax * t + bx) * t + cx,
+            dy = (ay * t + by) * t + cy;
+          return sqrt(dx * dx + dy * dy);
+        };
+
+        return integrate(
+          integrand, 0, 1, limit || Two.Utils.Curve.RecursionLimit
+        );
+
+      },
+
+      /**
+       * Integration for `getCurveLength` calculations. Referenced from
+       * Paper.js: https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101
+       */
+      integrate: function(f, a, b, n) {
+        var x = Two.Utils.Curve.abscissas[n - 2],
+          w = Two.Utils.Curve.weights[n - 2],
+          A = 0.5 * (b - a),
+          B = A + a,
+          i = 0,
+          m = (n + 1) >> 1,
+          sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n
+        while (i < m) {
+          var Ax = A * x[i];
+          sum += w[i++] * (f(B + Ax) + f(B - Ax));
+        }
+        return A * sum;
+      },
+
+      /**
+       * Creates a set of points that have u, v values for anchor positions
+       */
+      getCurveFromPoints: function(points, closed) {
+
+        var l = points.length, last = l - 1;
+
+        for (var i = 0; i < l; i++) {
+
+          var point = points[i];
+
+          if (!_.isObject(point.controls)) {
+            Two.Anchor.AppendCurveProperties(point);
+          }
+
+          var prev = closed ? mod(i - 1, l) : max(i - 1, 0);
+          var next = closed ? mod(i + 1, l) : min(i + 1, last);
+
+          var a = points[prev];
+          var b = point;
+          var c = points[next];
+          getControlPoints(a, b, c);
+
+          b._command = i === 0 ? Two.Commands.move : Two.Commands.curve;
+
+          b.controls.left.x = _.isNumber(b.controls.left.x) ? b.controls.left.x : b.x;
+          b.controls.left.y = _.isNumber(b.controls.left.y) ? b.controls.left.y : b.y;
+
+          b.controls.right.x = _.isNumber(b.controls.right.x) ? b.controls.right.x : b.x;
+          b.controls.right.y = _.isNumber(b.controls.right.y) ? b.controls.right.y : b.y;
+
+        }
+
+      },
+
+      /**
+       * Given three coordinates return the control points for the middle, b,
+       * vertex.
+       */
+      getControlPoints: function(a, b, c) {
+
+        var a1 = angleBetween(a, b);
+        var a2 = angleBetween(c, b);
+
+        var d1 = distanceBetween(a, b);
+        var d2 = distanceBetween(c, b);
+
+        var mid = (a1 + a2) / 2;
+
+        // So we know which angle corresponds to which side.
+
+        b.u = _.isObject(b.controls.left) ? b.controls.left : new Two.Vector(0, 0);
+        b.v = _.isObject(b.controls.right) ? b.controls.right : new Two.Vector(0, 0);
+
+        // TODO: Issue 73
+        if (d1 < 0.0001 || d2 < 0.0001) {
+          if (!b._relative) {
+            b.controls.left.copy(b);
+            b.controls.right.copy(b);
+          }
+          return b;
+        }
+
+        d1 *= 0.33; // Why 0.33?
+        d2 *= 0.33;
+
+        if (a2 < a1) {
+          mid += HALF_PI;
+        } else {
+          mid -= HALF_PI;
+        }
+
+        b.controls.left.x = cos(mid) * d1;
+        b.controls.left.y = sin(mid) * d1;
+
+        mid -= PI;
+
+        b.controls.right.x = cos(mid) * d2;
+        b.controls.right.y = sin(mid) * d2;
+
+        if (!b._relative) {
+          b.controls.left.x += b.x;
+          b.controls.left.y += b.y;
+          b.controls.right.x += b.x;
+          b.controls.right.y += b.y;
+        }
+
+        return b;
+
+      },
+
+      /**
+       * Get the reflection of a point "b" about point "a". Where "a" is in
+       * absolute space and "b" is relative to "a".
+       *
+       * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
+       */
+      getReflection: function(a, b, relative) {
+
+        return new Two.Vector(
+          2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
+          2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
+        );
+
+      },
+
+      getAnchorsFromArcData: function(center, xAxisRotation, rx, ry, ts, td, ccw) {
+
+        var matrix = new Two.Matrix()
+          .translate(center.x, center.y)
+          .rotate(xAxisRotation);
+
+        var l = Two.Resolution;
+
+        return _.map(_.range(l), function(i) {
+
+          var pct = (i + 1) / l;
+          if (!!ccw) {
+            pct = 1 - pct;
+          }
+
+          var theta = pct * td + ts;
+          var x = rx * Math.cos(theta);
+          var y = ry * Math.sin(theta);
+
+          // x += center.x;
+          // y += center.y;
+
+          var anchor = new Two.Anchor(x, y);
+          Two.Anchor.AppendCurveProperties(anchor);
+          anchor.command = Two.Commands.line;
+
+          // TODO: Calculate control points here...
+
+          return anchor;
+
+        });
+
+      },
+
+      ratioBetween: function(A, B) {
+
+        return (A.x * B.x + A.y * B.y) / (A.length() * B.length());
+
+      },
+
+      angleBetween: function(A, B) {
+
+        var dx, dy;
+
+        if (arguments.length >= 4) {
+
+          dx = arguments[0] - arguments[2];
+          dy = arguments[1] - arguments[3];
+
+          return atan2(dy, dx);
+
+        }
+
+        dx = A.x - B.x;
+        dy = A.y - B.y;
+
+        return atan2(dy, dx);
+
+      },
+
+      distanceBetweenSquared: function(p1, p2) {
+
+        var dx = p1.x - p2.x;
+        var dy = p1.y - p2.y;
+
+        return dx * dx + dy * dy;
+
+      },
+
+      distanceBetween: function(p1, p2) {
+
+        return sqrt(distanceBetweenSquared(p1, p2));
+
+      },
+
+      lerp: function(a, b, t) {
+        return t * (b - a) + a;
+      },
+
+      // A pretty fast toFixed(3) alternative
+      // See http://jsperf.com/parsefloat-tofixed-vs-math-round/18
+      toFixed: function(v) {
+        return Math.floor(v * 1000) / 1000;
+      },
+
+      mod: function(v, l) {
+
+        while (v < 0) {
+          v += l;
+        }
+
+        return v % l;
+
+      },
+
+      /**
+       * Array like collection that triggers inserted and removed events
+       * removed : pop / shift / splice
+       * inserted : push / unshift / splice (with > 2 arguments)
+       */
+      Collection: function() {
+
+        Array.call(this);
+
+        if (arguments.length > 1) {
+          Array.prototype.push.apply(this, arguments);
+        } else if (arguments[0] && Array.isArray(arguments[0])) {
+          Array.prototype.push.apply(this, arguments[0]);
+        }
+
+      },
+
+      // Custom Error Throwing for Two.js
+
+      Error: function(message) {
+        this.name = 'two.js';
+        this.message = message;
+      },
+
+      Events: {
+
+        on: function(name, callback) {
+
+          this._events || (this._events = {});
+          var list = this._events[name] || (this._events[name] = []);
+
+          list.push(callback);
+
+          return this;
+
+        },
+
+        off: function(name, callback) {
+
+          if (!this._events) {
+            return this;
+          }
+          if (!name && !callback) {
+            this._events = {};
+            return this;
+          }
+
+          var names = name ? [name] : _.keys(this._events);
+          for (var i = 0, l = names.length; i < l; i++) {
+
+            var name = names[i];
+            var list = this._events[name];
+
+            if (!!list) {
+              var events = [];
+              if (callback) {
+                for (var j = 0, k = list.length; j < k; j++) {
+                  var ev = list[j];
+                  ev = ev.callback ? ev.callback : ev;
+                  if (callback && callback !== ev) {
+                    events.push(ev);
+                  }
+                }
+              }
+              this._events[name] = events;
+            }
+          }
+
+          return this;
+        },
+
+        trigger: function(name) {
+          if (!this._events) return this;
+          var args = slice.call(arguments, 1);
+          var events = this._events[name];
+          if (events) trigger(this, events, args);
+          return this;
+        },
+
+        listen: function (obj, name, callback) {
+
+          var bound = this;
+
+          if (obj) {
+            var ev = function () {
+              callback.apply(bound, arguments);
+            };
+
+            // add references about the object that assigned this listener
+            ev.obj = obj;
+            ev.name = name;
+            ev.callback = callback;
+
+            obj.on(name, ev);
+          }
+
+          return this;
+
+        },
+
+        ignore: function (obj, name, callback) {
+
+          obj.off(name, callback);
+
+          return this;
+
+        }
+
+      }
+
+    })
+
+  });
+
+  Two.Utils.Events.bind = Two.Utils.Events.on;
+  Two.Utils.Events.unbind = Two.Utils.Events.off;
+
+  var trigger = function(obj, events, args) {
+    var method;
+    switch (args.length) {
+    case 0:
+      method = function(i) {
+        events[i].call(obj, args[0]);
+      };
+      break;
+    case 1:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1]);
+      };
+      break;
+    case 2:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1], args[2]);
+      };
+      break;
+    case 3:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1], args[2], args[3]);
+      };
+      break;
+    default:
+      method = function(i) {
+        events[i].apply(obj, args);
+      };
+    }
+    for (var i = 0; i < events.length; i++) {
+      method(i);
+    }
+  };
+
+  Two.Utils.Error.prototype = new Error();
+  Two.Utils.Error.prototype.constructor = Two.Utils.Error;
+
+  Two.Utils.Collection.prototype = new Array();
+  Two.Utils.Collection.prototype.constructor = Two.Utils.Collection;
+
+  _.extend(Two.Utils.Collection.prototype, Two.Utils.Events, {
+
+    pop: function() {
+      var popped = Array.prototype.pop.apply(this, arguments);
+      this.trigger(Two.Events.remove, [popped]);
+      return popped;
+    },
+
+    shift: function() {
+      var shifted = Array.prototype.shift.apply(this, arguments);
+      this.trigger(Two.Events.remove, [shifted]);
+      return shifted;
+    },
+
+    push: function() {
+      var pushed = Array.prototype.push.apply(this, arguments);
+      this.trigger(Two.Events.insert, arguments);
+      return pushed;
+    },
+
+    unshift: function() {
+      var unshifted = Array.prototype.unshift.apply(this, arguments);
+      this.trigger(Two.Events.insert, arguments);
+      return unshifted;
+    },
+
+    splice: function() {
+      var spliced = Array.prototype.splice.apply(this, arguments);
+      var inserted;
+
+      this.trigger(Two.Events.remove, spliced);
+
+      if (arguments.length > 2) {
+        inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
+        this.trigger(Two.Events.insert, inserted);
+        this.trigger(Two.Events.order);
+      }
+      return spliced;
+    },
+
+    sort: function() {
+      Array.prototype.sort.apply(this, arguments);
+      this.trigger(Two.Events.order);
+      return this;
+    },
+
+    reverse: function() {
+      Array.prototype.reverse.apply(this, arguments);
+      this.trigger(Two.Events.order);
+      return this;
+    }
+
+  });
+
+  // Localize utils
+
+  var distanceBetween = Two.Utils.distanceBetween,
+    getAnchorsFromArcData = Two.Utils.getAnchorsFromArcData,
+    distanceBetweenSquared = Two.Utils.distanceBetweenSquared,
+    ratioBetween = Two.Utils.ratioBetween,
+    angleBetween = Two.Utils.angleBetween,
+    getControlPoints = Two.Utils.getControlPoints,
+    getCurveFromPoints = Two.Utils.getCurveFromPoints,
+    solveSegmentIntersection = Two.Utils.solveSegmentIntersection,
+    decoupleShapes = Two.Utils.decoupleShapes,
+    mod = Two.Utils.mod,
+    getBackingStoreRatio = Two.Utils.getBackingStoreRatio,
+    getPointOnCubicBezier = Two.Utils.getPointOnCubicBezier,
+    getCurveLength = Two.Utils.getCurveLength,
+    integrate = Two.Utils.integrate,
+    getReflection = Two.Utils.getReflection;
+
+  _.extend(Two.prototype, Two.Utils.Events, {
+
+    appendTo: function(elem) {
+
+      elem.appendChild(this.renderer.domElement);
+      return this;
+
+    },
+
+    play: function() {
+
+      Two.Utils.setPlaying.call(this, true);
+      return this.trigger(Two.Events.play);
+
+    },
+
+    pause: function() {
+
+      this.playing = false;
+      return this.trigger(Two.Events.pause);
+
+    },
+
+    /**
+     * Update positions and calculations in one pass before rendering.
+     */
+    update: function() {
+
+      var animated = !!this._lastFrame;
+      var now = perf.now();
+
+      this.frameCount++;
+
+      if (animated) {
+        this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
+      }
+      this._lastFrame = now;
+
+      var width = this.width;
+      var height = this.height;
+      var renderer = this.renderer;
+
+      // Update width / height for the renderer
+      if (width !== renderer.width || height !== renderer.height) {
+        renderer.setSize(width, height, this.ratio);
+      }
+
+      this.trigger(Two.Events.update, this.frameCount, this.timeDelta);
+
+      return this.render();
+
+    },
+
+    /**
+     * Render all drawable - visible objects of the scene.
+     */
+    render: function() {
+
+      this.renderer.render();
+      return this.trigger(Two.Events.render, this.frameCount);
+
+    },
+
+    /**
+     * Convenience Methods
+     */
+
+    add: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = _.toArray(arguments);
+      }
+
+      this.scene.add(objects);
+      return this;
+
+    },
+
+    remove: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = _.toArray(arguments);
+      }
+
+      this.scene.remove(objects);
+
+      return this;
+
+    },
+
+    clear: function() {
+
+      this.scene.remove(_.toArray(this.scene.children));
+      return this;
+
+    },
+
+    makeLine: function(x1, y1, x2, y2) {
+
+      var line = new Two.Line(x1, y1, x2, y2);
+      this.scene.add(line);
+
+      return line;
+
+    },
+
+    makeRectangle: function(x, y, width, height) {
+
+      var rect = new Two.Rectangle(x, y, width, height);
+      this.scene.add(rect);
+
+      return rect;
+
+    },
+
+    makeRoundedRectangle: function(x, y, width, height, sides) {
+
+      var rect = new Two.RoundedRectangle(x, y, width, height, sides);
+      this.scene.add(rect);
+
+      return rect;
+
+    },
+
+    makeCircle: function(ox, oy, r) {
+
+      var circle = new Two.Circle(ox, oy, r);
+      this.scene.add(circle);
+
+      return circle;
+
+    },
+
+    makeEllipse: function(ox, oy, rx, ry) {
+
+      var ellipse = new Two.Ellipse(ox, oy, rx, ry);
+      this.scene.add(ellipse);
+
+      return ellipse;
+
+    },
+
+    makeStar: function(ox, oy, or, ir, sides) {
+
+      var star = new Two.Star(ox, oy, or, ir, sides);
+      this.scene.add(star);
+
+      return star;
+
+    },
+
+    makeCurve: function(p) {
+
+      var l = arguments.length, points = p;
+      if (!_.isArray(p)) {
+        points = [];
+        for (var i = 0; i < l; i+=2) {
+          var x = arguments[i];
+          if (!_.isNumber(x)) {
+            break;
+          }
+          var y = arguments[i + 1];
+          points.push(new Two.Anchor(x, y));
+        }
+      }
+
+      var last = arguments[l - 1];
+      var curve = new Two.Path(points, !(_.isBoolean(last) ? last : undefined), true);
+      var rect = curve.getBoundingClientRect();
+      curve.center().translation
+        .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+
+      this.scene.add(curve);
+
+      return curve;
+
+    },
+
+    makePolygon: function(ox, oy, r, sides) {
+
+      var poly = new Two.Polygon(ox, oy, r, sides);
+      this.scene.add(poly);
+
+      return poly;
+
+    },
+
+    /*
+    * Make an Arc Segment
+    */
+
+    makeArcSegment: function(ox, oy, ir, or, sa, ea, res) {
+      var arcSegment = new Two.ArcSegment(ox, oy, ir, or, sa, ea, res);
+      this.scene.add(arcSegment);
+      return arcSegment;
+    },
+
+    /**
+     * Convenience method to make and draw a Two.Path.
+     */
+    makePath: function(p) {
+
+      var l = arguments.length, points = p;
+      if (!_.isArray(p)) {
+        points = [];
+        for (var i = 0; i < l; i+=2) {
+          var x = arguments[i];
+          if (!_.isNumber(x)) {
+            break;
+          }
+          var y = arguments[i + 1];
+          points.push(new Two.Anchor(x, y));
+        }
+      }
+
+      var last = arguments[l - 1];
+      var path = new Two.Path(points, !(_.isBoolean(last) ? last : undefined));
+      var rect = path.getBoundingClientRect();
+      path.center().translation
+        .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+
+      this.scene.add(path);
+
+      return path;
+
+    },
+
+    /**
+     * Convenience method to make and add a Two.Text.
+     */
+    makeText: function(message, x, y, styles) {
+      var text = new Two.Text(message, x, y, styles);
+      this.add(text);
+      return text;
+    },
+
+    /**
+     * Convenience method to make and add a Two.LinearGradient.
+     */
+    makeLinearGradient: function(x1, y1, x2, y2 /* stops */) {
+
+      var stops = slice.call(arguments, 4);
+      var gradient = new Two.LinearGradient(x1, y1, x2, y2, stops);
+
+      this.add(gradient);
+
+      return gradient;
+
+    },
+
+    /**
+     * Convenience method to make and add a Two.RadialGradient.
+     */
+    makeRadialGradient: function(x1, y1, r /* stops */) {
+
+      var stops = slice.call(arguments, 3);
+      var gradient = new Two.RadialGradient(x1, y1, r, stops);
+
+      this.add(gradient);
+
+      return gradient;
+
+    },
+
+    makeSprite: function(path, x, y, cols, rows, frameRate, autostart) {
+
+      var sprite = new Two.Sprite(path, x, y, cols, rows, frameRate);
+      if (!!autostart) {
+        sprite.play();
+      }
+      this.add(sprite);
+
+      return sprite;
+
+    },
+
+    makeImageSequence: function(paths, x, y, frameRate, autostart) {
+
+      var imageSequence = new Two.ImageSequence(paths, x, y, frameRate);
+      if (!!autostart) {
+        imageSequence.play();
+      }
+      this.add(imageSequence);
+
+      return imageSequence;
+
+    },
+
+    makeTexture: function(path, callback) {
+
+      var texture = new Two.Texture(path, callback);
+      return texture;
+
+    },
+
+    makeGroup: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = _.toArray(arguments);
+      }
+
+      var group = new Two.Group();
+      this.scene.add(group);
+      group.add(objects);
+
+      return group;
+
+    },
+
+    /**
+     * Interpret an SVG Node and add it to this instance's scene. The
+     * distinction should be made that this doesn't `import` svg's, it solely
+     * interprets them into something compatible for Two.js — this is slightly
+     * different than a direct transcription.
+     *
+     * @param {Object} svgNode - The node to be parsed
+     * @param {Boolean} shallow - Don't create a top-most group but
+     *                                    append all contents directly
+     */
+    interpret: function(svgNode, shallow) {
+
+      var tag = svgNode.tagName.toLowerCase();
+
+      if (!(tag in Two.Utils.read)) {
+        return null;
+      }
+
+      var node = Two.Utils.read[tag].call(this, svgNode);
+
+      if (shallow && node instanceof Two.Group) {
+        this.add(node.children);
+      } else {
+        this.add(node);
+      }
+
+      return node;
+
+    },
+
+    /**
+     * Load an SVG file / text and interpret.
+     */
+    load: function(text, callback) {
+
+      var nodes = [], elem, i;
+
+      if (/.*\.svg/ig.test(text)) {
+
+        Two.Utils.xhr(text, _.bind(function(data) {
+
+          dom.temp.innerHTML = data;
+          for (i = 0; i < dom.temp.children.length; i++) {
+            elem = dom.temp.children[i];
+            nodes.push(this.interpret(elem));
+          }
+
+          callback(nodes.length <= 1 ? nodes[0] : nodes,
+            dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
+
+        }, this));
+
+        return this;
+
+      }
+
+      dom.temp.innerHTML = text;
+      for (i = 0; i < dom.temp.children.length; i++) {
+        elem = dom.temp.children[i];
+        nodes.push(this.interpret(elem));
+      }
+
+      callback(nodes.length <= 1 ? nodes[0] : nodes,
+        dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
+
+      return this;
+
+    }
+
+  });
+
+  function fitToWindow() {
+
+    var wr = document.body.getBoundingClientRect();
+
+    var width = this.width = wr.width;
+    var height = this.height = wr.height;
+
+    this.renderer.setSize(width, height, this.ratio);
+    this.trigger(Two.Events.resize, width, height);
+
+  }
+
+  // Request Animation Frame
+
+  var raf = dom.getRequestAnimationFrame();
+
+  function loop() {
+
+    raf(loop);
+
+    for (var i = 0; i < Two.Instances.length; i++) {
+      var t = Two.Instances[i];
+      if (t.playing) {
+        t.update();
+      }
+    }
+
+  }
+
+  if (typeof define === 'function' && define.amd) {
+    define('two', [], function() {
+      return Two;
+    });
+  } else if (typeof module != 'undefined' && module.exports) {
+    module.exports = Two;
+  }
+
+  return Two;
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var Registry = Two.Registry = function() {
+
+    this.map = {};
+
+  };
+
+  _.extend(Registry, {
+
+  });
+
+  _.extend(Registry.prototype, {
+
+    add: function(id, obj) {
+      this.map[id] = obj;
+      return this;
+    },
+
+    remove: function(id) {
+      delete this.map[id];
+      return this;
+    },
+
+    get: function(id) {
+      return this.map[id];
+    },
+
+    contains: function(id) {
+      return id in this.map;
+    }
+
+  });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var Vector = Two.Vector = function(x, y) {
+
+    this.x = x || 0;
+    this.y = y || 0;
+
+  };
+
+  _.extend(Vector, {
+
+    zero: new Two.Vector()
+
+  });
+
+  _.extend(Vector.prototype, Two.Utils.Events, {
+
+    set: function(x, y) {
+      this.x = x;
+      this.y = y;
+      return this;
+    },
+
+    copy: function(v) {
+      this.x = v.x;
+      this.y = v.y;
+      return this;
+    },
+
+    clear: function() {
+      this.x = 0;
+      this.y = 0;
+      return this;
+    },
+
+    clone: function() {
+      return new Vector(this.x, this.y);
+    },
+
+    add: function(v1, v2) {
+      this.x = v1.x + v2.x;
+      this.y = v1.y + v2.y;
+      return this;
+    },
+
+    addSelf: function(v) {
+      this.x += v.x;
+      this.y += v.y;
+      return this;
+    },
+
+    sub: function(v1, v2) {
+      this.x = v1.x - v2.x;
+      this.y = v1.y - v2.y;
+      return this;
+    },
+
+    subSelf: function(v) {
+      this.x -= v.x;
+      this.y -= v.y;
+      return this;
+    },
+
+    multiplySelf: function(v) {
+      this.x *= v.x;
+      this.y *= v.y;
+      return this;
+    },
+
+    multiplyScalar: function(s) {
+      this.x *= s;
+      this.y *= s;
+      return this;
+    },
+
+    divideScalar: function(s) {
+      if (s) {
+        this.x /= s;
+        this.y /= s;
+      } else {
+        this.set(0, 0);
+      }
+      return this;
+    },
+
+    negate: function() {
+      return this.multiplyScalar(-1);
+    },
+
+    dot: function(v) {
+      return this.x * v.x + this.y * v.y;
+    },
+
+    lengthSquared: function() {
+      return this.x * this.x + this.y * this.y;
+    },
+
+    length: function() {
+      return Math.sqrt(this.lengthSquared());
+    },
+
+    normalize: function() {
+      return this.divideScalar(this.length());
+    },
+
+    distanceTo: function(v) {
+      return Math.sqrt(this.distanceToSquared(v));
+    },
+
+    distanceToSquared: function(v) {
+      var dx = this.x - v.x,
+          dy = this.y - v.y;
+      return dx * dx + dy * dy;
+    },
+
+    setLength: function(l) {
+      return this.normalize().multiplyScalar(l);
+    },
+
+    equals: function(v, eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.distanceTo(v) < eps);
+    },
+
+    lerp: function(v, t) {
+      var x = (v.x - this.x) * t + this.x;
+      var y = (v.y - this.y) * t + this.y;
+      return this.set(x, y);
+    },
+
+    isZero: function(eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.length() < eps);
+    },
+
+    toString: function() {
+      return this.x + ', ' + this.y;
+    },
+
+    toObject: function() {
+      return { x: this.x, y: this.y };
+    },
+
+    rotate: function (radians) {
+      var cos = Math.cos(radians);
+      var sin = Math.sin(radians);
+      this.x = this.x * cos - this.y * sin;
+      this.y = this.x * sin + this.y * cos;
+      return this;
+    }
+
+  });
+
+  var BoundProto = {
+
+    set: function(x, y) {
+      this._x = x;
+      this._y = y;
+      return this.trigger(Two.Events.change);
+    },
+
+    copy: function(v) {
+      this._x = v.x;
+      this._y = v.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    clear: function() {
+      this._x = 0;
+      this._y = 0;
+      return this.trigger(Two.Events.change);
+    },
+
+    clone: function() {
+      return new Vector(this._x, this._y);
+    },
+
+    add: function(v1, v2) {
+      this._x = v1.x + v2.x;
+      this._y = v1.y + v2.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    addSelf: function(v) {
+      this._x += v.x;
+      this._y += v.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    sub: function(v1, v2) {
+      this._x = v1.x - v2.x;
+      this._y = v1.y - v2.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    subSelf: function(v) {
+      this._x -= v.x;
+      this._y -= v.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    multiplySelf: function(v) {
+      this._x *= v.x;
+      this._y *= v.y;
+      return this.trigger(Two.Events.change);
+    },
+
+    multiplyScalar: function(s) {
+      this._x *= s;
+      this._y *= s;
+      return this.trigger(Two.Events.change);
+    },
+
+    divideScalar: function(s) {
+      if (s) {
+        this._x /= s;
+        this._y /= s;
+        return this.trigger(Two.Events.change);
+      }
+      return this.clear();
+    },
+
+    negate: function() {
+      return this.multiplyScalar(-1);
+    },
+
+    dot: function(v) {
+      return this._x * v.x + this._y * v.y;
+    },
+
+    lengthSquared: function() {
+      return this._x * this._x + this._y * this._y;
+    },
+
+    length: function() {
+      return Math.sqrt(this.lengthSquared());
+    },
+
+    normalize: function() {
+      return this.divideScalar(this.length());
+    },
+
+    distanceTo: function(v) {
+      return Math.sqrt(this.distanceToSquared(v));
+    },
+
+    distanceToSquared: function(v) {
+      var dx = this._x - v.x,
+          dy = this._y - v.y;
+      return dx * dx + dy * dy;
+    },
+
+    setLength: function(l) {
+      return this.normalize().multiplyScalar(l);
+    },
+
+    equals: function(v, eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.distanceTo(v) < eps);
+    },
+
+    lerp: function(v, t) {
+      var x = (v.x - this._x) * t + this._x;
+      var y = (v.y - this._y) * t + this._y;
+      return this.set(x, y);
+    },
+
+    isZero: function(eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.length() < eps);
+    },
+
+    toString: function() {
+      return this._x + ', ' + this._y;
+    },
+
+    toObject: function() {
+      return { x: this._x, y: this._y };
+    },
+
+    rotate: function (radians) {
+      var cos = Math.cos(radians);
+      var sin = Math.sin(radians);
+      this._x = this._x * cos - this._y * sin;
+      this._y = this._x * sin + this._y * cos;
+      return this;
+    }
+
+  };
+
+  var xgs = {
+    enumerable: true,
+    get: function() {
+      return this._x;
+    },
+    set: function(v) {
+      this._x = v;
+      this.trigger(Two.Events.change, 'x');
+    }
+  };
+
+  var ygs = {
+    enumerable: true,
+    get: function() {
+      return this._y;
+    },
+    set: function(v) {
+      this._y = v;
+      this.trigger(Two.Events.change, 'y');
+    }
+  };
+
+  /**
+   * Override Backbone bind / on in order to add properly broadcasting.
+   * This allows Two.Vector to not broadcast events unless event listeners
+   * are explicity bound to it.
+   */
+
+  Two.Vector.prototype.bind = Two.Vector.prototype.on = function() {
+
+    if (!this._bound) {
+      this._x = this.x;
+      this._y = this.y;
+      Object.defineProperty(this, 'x', xgs);
+      Object.defineProperty(this, 'y', ygs);
+      _.extend(this, BoundProto);
+      this._bound = true; // Reserved for event initialization check
+    }
+
+    Two.Utils.Events.bind.apply(this, arguments);
+
+    return this;
+
+  };
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  // Localized variables
+  var commands = Two.Commands;
+  var _ = Two.Utils;
+
+  /**
+   * An object that holds 3 `Two.Vector`s, the anchor point and its
+   * corresponding handles: `left` and `right`.
+   */
+  var Anchor = Two.Anchor = function(x, y, ux, uy, vx, vy, command) {
+
+    Two.Vector.call(this, x, y);
+
+    this._broadcast = _.bind(function() {
+      this.trigger(Two.Events.change);
+    }, this);
+
+    this._command = command || commands.move;
+    this._relative = true;
+
+    if (!command) {
+      return this;
+    }
+
+    Anchor.AppendCurveProperties(this);
+
+    if (_.isNumber(ux)) {
+      this.controls.left.x = ux;
+    }
+    if (_.isNumber(uy)) {
+      this.controls.left.y = uy;
+    }
+    if (_.isNumber(vx)) {
+      this.controls.right.x = vx;
+    }
+    if (_.isNumber(vy)) {
+      this.controls.right.y = vy;
+    }
+
+  };
+
+  _.extend(Anchor, {
+
+    AppendCurveProperties: function(anchor) {
+      anchor.controls = {
+        left: new Two.Vector(0, 0),
+        right: new Two.Vector(0, 0)
+      };
+    }
+
+  });
+
+  var AnchorProto = {
+
+    listen: function() {
+
+      if (!_.isObject(this.controls)) {
+        Anchor.AppendCurveProperties(this);
+      }
+
+      this.controls.left.bind(Two.Events.change, this._broadcast);
+      this.controls.right.bind(Two.Events.change, this._broadcast);
+
+      return this;
+
+    },
+
+    ignore: function() {
+
+      this.controls.left.unbind(Two.Events.change, this._broadcast);
+      this.controls.right.unbind(Two.Events.change, this._broadcast);
+
+      return this;
+
+    },
+
+    clone: function() {
+
+      var controls = this.controls;
+
+      var clone = new Two.Anchor(
+        this.x,
+        this.y,
+        controls && controls.left.x,
+        controls && controls.left.y,
+        controls && controls.right.x,
+        controls && controls.right.y,
+        this.command
+      );
+      clone.relative = this._relative;
+      return clone;
+
+    },
+
+    toObject: function() {
+      var o = {
+        x: this.x,
+        y: this.y
+      };
+      if (this._command) {
+        o.command = this._command;
+      }
+      if (this._relative) {
+        o.relative = this._relative;
+      }
+      if (this.controls) {
+        o.controls = {
+          left: this.controls.left.toObject(),
+          right: this.controls.right.toObject()
+        };
+      }
+      return o;
+    },
+
+    toString: function() {
+      if (!this.controls) {
+        return [this._x, this._y].join(', ');
+      }
+      return [this._x, this._y, this.controls.left.x, this.controls.left.y,
+        this.controls.right.x, this.controls.right.y].join(', ');
+    }
+
+  };
+
+  Object.defineProperty(Anchor.prototype, 'command', {
+
+    enumerable: true,
+
+    get: function() {
+      return this._command;
+    },
+
+    set: function(c) {
+      this._command = c;
+      if (this._command === commands.curve && !_.isObject(this.controls)) {
+        Anchor.AppendCurveProperties(this);
+      }
+      return this.trigger(Two.Events.change);
+    }
+
+  });
+
+  Object.defineProperty(Anchor.prototype, 'relative', {
+
+    enumerable: true,
+
+    get: function() {
+      return this._relative;
+    },
+
+    set: function(b) {
+      if (this._relative == b) {
+        return this;
+      }
+      this._relative = !!b;
+      return this.trigger(Two.Events.change);
+    }
+
+  });
+
+  _.extend(Anchor.prototype, Two.Vector.prototype, AnchorProto);
+
+  // Make it possible to bind and still have the Anchor specific
+  // inheritance from Two.Vector
+  Two.Anchor.prototype.bind = Two.Anchor.prototype.on = function() {
+    Two.Vector.prototype.bind.apply(this, arguments);
+    _.extend(this, AnchorProto);
+  };
+
+  Two.Anchor.prototype.unbind = Two.Anchor.prototype.off = function() {
+    Two.Vector.prototype.unbind.apply(this, arguments);
+    _.extend(this, AnchorProto);
+  };
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  /**
+   * Constants
+   */
+  var cos = Math.cos, sin = Math.sin, tan = Math.tan;
+  var _ = Two.Utils;
+
+  /**
+   * Two.Matrix contains an array of elements that represent
+   * the two dimensional 3 x 3 matrix as illustrated below:
+   *
+   * =====
+   * a b c
+   * d e f
+   * g h i  // this row is not really used in 2d transformations
+   * =====
+   *
+   * String order is for transform strings: a, d, b, e, c, f
+   *
+   * @class
+   */
+  var Matrix = Two.Matrix = function(a, b, c, d, e, f) {
+
+    this.elements = new Two.Array(9);
+
+    var elements = a;
+    if (!_.isArray(elements)) {
+      elements = _.toArray(arguments);
+    }
+
+    // initialize the elements with default values.
+
+    this.identity().set(elements);
+
+  };
+
+  _.extend(Matrix, {
+
+    Identity: [
+      1, 0, 0,
+      0, 1, 0,
+      0, 0, 1
+    ],
+
+    /**
+     * Multiply two matrix 3x3 arrays
+     */
+    Multiply: function(A, B, C) {
+
+      if (B.length <= 3) { // Multiply Vector
+
+        var x, y, z, e = A;
+
+        var a = B[0] || 0,
+            b = B[1] || 0,
+            c = B[2] || 0;
+
+        // Go down rows first
+        // a, d, g, b, e, h, c, f, i
+
+        x = e[0] * a + e[1] * b + e[2] * c;
+        y = e[3] * a + e[4] * b + e[5] * c;
+        z = e[6] * a + e[7] * b + e[8] * c;
+
+        return { x: x, y: y, z: z };
+
+      }
+
+      var A0 = A[0], A1 = A[1], A2 = A[2];
+      var A3 = A[3], A4 = A[4], A5 = A[5];
+      var A6 = A[6], A7 = A[7], A8 = A[8];
+
+      var B0 = B[0], B1 = B[1], B2 = B[2];
+      var B3 = B[3], B4 = B[4], B5 = B[5];
+      var B6 = B[6], B7 = B[7], B8 = B[8];
+
+      C = C || new Two.Array(9);
+
+      C[0] = A0 * B0 + A1 * B3 + A2 * B6;
+      C[1] = A0 * B1 + A1 * B4 + A2 * B7;
+      C[2] = A0 * B2 + A1 * B5 + A2 * B8;
+      C[3] = A3 * B0 + A4 * B3 + A5 * B6;
+      C[4] = A3 * B1 + A4 * B4 + A5 * B7;
+      C[5] = A3 * B2 + A4 * B5 + A5 * B8;
+      C[6] = A6 * B0 + A7 * B3 + A8 * B6;
+      C[7] = A6 * B1 + A7 * B4 + A8 * B7;
+      C[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+      return C;
+
+    }
+
+  });
+
+  _.extend(Matrix.prototype, Two.Utils.Events, {
+
+    /**
+     * Takes an array of elements or the arguments list itself to
+     * set and update the current matrix's elements. Only updates
+     * specified values.
+     */
+    set: function(a) {
+
+      var elements = a;
+      if (!_.isArray(elements)) {
+        elements = _.toArray(arguments);
+      }
+
+      _.extend(this.elements, elements);
+
+      return this.trigger(Two.Events.change);
+
+    },
+
+    /**
+     * Turn matrix to identity, like resetting.
+     */
+    identity: function() {
+
+      this.set(Matrix.Identity);
+
+      return this;
+
+    },
+
+    /**
+     * Multiply scalar or multiply by another matrix.
+     */
+    multiply: function(a, b, c, d, e, f, g, h, i) {
+
+      var elements = arguments, l = elements.length;
+
+      // Multiply scalar
+
+      if (l <= 1) {
+
+        _.each(this.elements, function(v, i) {
+          this.elements[i] = v * a;
+        }, this);
+
+        return this.trigger(Two.Events.change);
+
+      }
+
+      if (l <= 3) { // Multiply Vector
+
+        var x, y, z;
+        a = a || 0;
+        b = b || 0;
+        c = c || 0;
+        e = this.elements;
+
+        // Go down rows first
+        // a, d, g, b, e, h, c, f, i
+
+        x = e[0] * a + e[1] * b + e[2] * c;
+        y = e[3] * a + e[4] * b + e[5] * c;
+        z = e[6] * a + e[7] * b + e[8] * c;
+
+        return { x: x, y: y, z: z };
+
+      }
+
+      // Multiple matrix
+
+      var A = this.elements;
+      var B = elements;
+
+      var A0 = A[0], A1 = A[1], A2 = A[2];
+      var A3 = A[3], A4 = A[4], A5 = A[5];
+      var A6 = A[6], A7 = A[7], A8 = A[8];
+
+      var B0 = B[0], B1 = B[1], B2 = B[2];
+      var B3 = B[3], B4 = B[4], B5 = B[5];
+      var B6 = B[6], B7 = B[7], B8 = B[8];
+
+      this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6;
+      this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7;
+      this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8;
+
+      this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6;
+      this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7;
+      this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8;
+
+      this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6;
+      this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7;
+      this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+      return this.trigger(Two.Events.change);
+
+    },
+
+    inverse: function(out) {
+
+      var a = this.elements;
+      out = out || new Two.Matrix();
+
+      var a00 = a[0], a01 = a[1], a02 = a[2];
+      var a10 = a[3], a11 = a[4], a12 = a[5];
+      var a20 = a[6], a21 = a[7], a22 = a[8];
+
+      var b01 = a22 * a11 - a12 * a21;
+      var b11 = -a22 * a10 + a12 * a20;
+      var b21 = a21 * a10 - a11 * a20;
+
+      // Calculate the determinant
+      var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+      if (!det) {
+        return null;
+      }
+
+      det = 1.0 / det;
+
+      out.elements[0] = b01 * det;
+      out.elements[1] = (-a22 * a01 + a02 * a21) * det;
+      out.elements[2] = (a12 * a01 - a02 * a11) * det;
+      out.elements[3] = b11 * det;
+      out.elements[4] = (a22 * a00 - a02 * a20) * det;
+      out.elements[5] = (-a12 * a00 + a02 * a10) * det;
+      out.elements[6] = b21 * det;
+      out.elements[7] = (-a21 * a00 + a01 * a20) * det;
+      out.elements[8] = (a11 * a00 - a01 * a10) * det;
+
+      return out;
+
+    },
+
+    /**
+     * Set a scalar onto the matrix.
+     */
+    scale: function(sx, sy) {
+
+      var l = arguments.length;
+      if (l <= 1) {
+        sy = sx;
+      }
+
+      return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * Rotate the matrix.
+     */
+    rotate: function(radians) {
+
+      var c = cos(radians);
+      var s = sin(radians);
+
+      return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * Translate the matrix.
+     */
+    translate: function(x, y) {
+
+      return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
+
+    },
+
+    /*
+     * Skew the matrix by an angle in the x axis direction.
+     */
+    skewX: function(radians) {
+
+      var a = tan(radians);
+
+      return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
+
+    },
+
+    /*
+     * Skew the matrix by an angle in the y axis direction.
+     */
+    skewY: function(radians) {
+
+      var a = tan(radians);
+
+      return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * Create a transform string to be used with rendering apis.
+     */
+    toString: function(fullMatrix) {
+      var temp = [];
+
+      this.toArray(fullMatrix, temp);
+
+      return temp.join(' ');
+
+    },
+
+    /**
+     * Create a transform array to be used with rendering apis.
+     */
+    toArray: function(fullMatrix, output) {
+
+     var elements = this.elements;
+     var hasOutput = !!output;
+
+     var a = parseFloat(elements[0].toFixed(3));
+     var b = parseFloat(elements[1].toFixed(3));
+     var c = parseFloat(elements[2].toFixed(3));
+     var d = parseFloat(elements[3].toFixed(3));
+     var e = parseFloat(elements[4].toFixed(3));
+     var f = parseFloat(elements[5].toFixed(3));
+
+      if (!!fullMatrix) {
+
+        var g = parseFloat(elements[6].toFixed(3));
+        var h = parseFloat(elements[7].toFixed(3));
+        var i = parseFloat(elements[8].toFixed(3));
+
+        if (hasOutput) {
+          output[0] = a;
+          output[1] = d;
+          output[2] = g;
+          output[3] = b;
+          output[4] = e;
+          output[5] = h;
+          output[6] = c;
+          output[7] = f;
+          output[8] = i;
+          return;
+        }
+
+        return [
+          a, d, g, b, e, h, c, f, i
+        ];
+      }
+
+      if (hasOutput) {
+        output[0] = a;
+        output[1] = d;
+        output[2] = b;
+        output[3] = e;
+        output[4] = c;
+        output[5] = f;
+        return;
+      }
+
+      return [
+        a, d, b, e, c, f  // Specific format see LN:19
+      ];
+
+    },
+
+    /**
+     * Clone the current matrix.
+     */
+    clone: function() {
+      var a, b, c, d, e, f, g, h, i;
+
+      a = this.elements[0];
+      b = this.elements[1];
+      c = this.elements[2];
+      d = this.elements[3];
+      e = this.elements[4];
+      f = this.elements[5];
+      g = this.elements[6];
+      h = this.elements[7];
+      i = this.elements[8];
+
+      return new Two.Matrix(a, b, c, d, e, f, g, h, i);
+
+    }
+
+  });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  // Localize variables
+  var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
+  var _ = Two.Utils;
+
+  var svg = {
+
+    version: 1.1,
+
+    ns: 'http://www.w3.org/2000/svg',
+    xlink: 'http://www.w3.org/1999/xlink',
+
+    alignments: {
+      left: 'start',
+      center: 'middle',
+      right: 'end'
+    },
+
+    /**
+     * Create an svg namespaced element.
+     */
+    createElement: function(name, attrs) {
+      var tag = name;
+      var elem = document.createElementNS(svg.ns, tag);
+      if (tag === 'svg') {
+        attrs = _.defaults(attrs || {}, {
+          version: svg.version
+        });
+      }
+      if (!_.isEmpty(attrs)) {
+        svg.setAttributes(elem, attrs);
+      }
+      return elem;
+    },
+
+    /**
+     * Add attributes from an svg element.
+     */
+    setAttributes: function(elem, attrs) {
+      var keys = Object.keys(attrs);
+      for (var i = 0; i < keys.length; i++) {
+        if (/href/.test(keys[i])) {
+          elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]);
+        } else {
+          elem.setAttribute(keys[i], attrs[keys[i]]);
+        }
+      }
+      return this;
+    },
+
+    /**
+     * Remove attributes from an svg element.
+     */
+    removeAttributes: function(elem, attrs) {
+      for (var key in attrs) {
+        elem.removeAttribute(key);
+      }
+      return this;
+    },
+
+    /**
+     * Turn a set of vertices into a string for the d property of a path
+     * element. It is imperative that the string collation is as fast as
+     * possible, because this call will be happening multiple times a
+     * second.
+     */
+    toString: function(points, closed) {
+
+      var l = points.length,
+        last = l - 1,
+        d, // The elusive last Two.Commands.move point
+        ret = '';
+
+      for (var i = 0; i < l; i++) {
+        var b = points[i];
+        var command;
+        var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0);
+        var next = closed ? mod(i + 1, l) : Math.min(i + 1, last);
+
+        var a = points[prev];
+        var c = points[next];
+
+        var vx, vy, ux, uy, ar, bl, br, cl;
+
+        // Access x and y directly,
+        // bypassing the getter
+        var x = toFixed(b._x);
+        var y = toFixed(b._y);
+
+        switch (b._command) {
+
+          case Two.Commands.close:
+            command = Two.Commands.close;
+            break;
+
+          case Two.Commands.curve:
+
+            ar = (a.controls && a.controls.right) || Two.Vector.zero;
+            bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+            if (a._relative) {
+              vx = toFixed((ar.x + a.x));
+              vy = toFixed((ar.y + a.y));
+            } else {
+              vx = toFixed(ar.x);
+              vy = toFixed(ar.y);
+            }
+
+            if (b._relative) {
+              ux = toFixed((bl.x + b.x));
+              uy = toFixed((bl.y + b.y));
+            } else {
+              ux = toFixed(bl.x);
+              uy = toFixed(bl.y);
+            }
+
+            command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) +
+              ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+            break;
+
+          case Two.Commands.move:
+            d = b;
+            command = Two.Commands.move + ' ' + x + ' ' + y;
+            break;
+
+          default:
+            command = b._command + ' ' + x + ' ' + y;
+
+        }
+
+        // Add a final point and close it off
+
+        if (i >= last && closed) {
+
+          if (b._command === Two.Commands.curve) {
+
+            // Make sure we close to the most previous Two.Commands.move
+            c = d;
+
+            br = (b.controls && b.controls.right) || b;
+            cl = (c.controls && c.controls.left) || c;
+
+            if (b._relative) {
+              vx = toFixed((br.x + b.x));
+              vy = toFixed((br.y + b.y));
+            } else {
+              vx = toFixed(br.x);
+              vy = toFixed(br.y);
+            }
+
+            if (c._relative) {
+              ux = toFixed((cl.x + c.x));
+              uy = toFixed((cl.y + c.y));
+            } else {
+              ux = toFixed(cl.x);
+              uy = toFixed(cl.y);
+            }
+
+            x = toFixed(c.x);
+            y = toFixed(c.y);
+
+            command +=
+              ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+          }
+
+          command += ' Z';
+
+        }
+
+        ret += command + ' ';
+
+      }
+
+      return ret;
+
+    },
+
+    getClip: function(shape) {
+
+      var clip = shape._renderer.clip;
+
+      if (!clip) {
+
+        var root = shape;
+
+        while (root.parent) {
+          root = root.parent;
+        }
+
+        clip = shape._renderer.clip = svg.createElement('clipPath');
+        root.defs.appendChild(clip);
+
+      }
+
+      return clip;
+
+    },
+
+    group: {
+
+      // TODO: Can speed up.
+      // TODO: How does this effect a f
+      appendChild: function(object) {
+
+        var elem = object._renderer.elem;
+
+        if (!elem) {
+          return;
+        }
+
+        var tag = elem.nodeName;
+
+        if (!tag || /(radial|linear)gradient/i.test(tag) || object._clip) {
+          return;
+        }
+
+        this.elem.appendChild(elem);
+
+      },
+
+      removeChild: function(object) {
+
+        var elem = object._renderer.elem;
+
+        if (!elem || elem.parentNode != this.elem) {
+          return;
+        }
+
+        var tag = elem.nodeName;
+
+        if (!tag) {
+          return;
+        }
+
+        // Defer subtractions while clipping.
+        if (object._clip) {
+          return;
+        }
+
+        this.elem.removeChild(elem);
+
+      },
+
+      orderChild: function(object) {
+        this.elem.appendChild(object._renderer.elem);
+      },
+
+      renderChild: function(child) {
+        svg[child._renderer.type].render.call(child, this);
+      },
+
+      render: function(domElement) {
+
+        this._update();
+
+        // Shortcut for hidden objects.
+        // Doesn't reset the flags, so changes are stored and
+        // applied once the object is visible again
+        if (this._opacity === 0 && !this._flagOpacity) {
+          return this;
+        }
+
+        if (!this._renderer.elem) {
+          this._renderer.elem = svg.createElement('g', {
+            id: this.id
+          });
+          domElement.appendChild(this._renderer.elem);
+        }
+
+        // _Update styles for the <g>
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+        var context = {
+          domElement: domElement,
+          elem: this._renderer.elem
+        };
+
+        if (flagMatrix) {
+          this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')');
+        }
+
+        for (var i = 0; i < this.children.length; i++) {
+          var child = this.children[i];
+          svg[child._renderer.type].render.call(child, domElement);
+        }
+
+        if (this._flagOpacity) {
+          this._renderer.elem.setAttribute('opacity', this._opacity);
+        }
+
+        if (this._flagAdditions) {
+          this.additions.forEach(svg.group.appendChild, context);
+        }
+
+        if (this._flagSubtractions) {
+          this.subtractions.forEach(svg.group.removeChild, context);
+        }
+
+        if (this._flagOrder) {
+          this.children.forEach(svg.group.orderChild, context);
+        }
+
+        /**
+         * Commented two-way functionality of clips / masks with groups and
+         * polygons. Uncomment when this bug is fixed:
+         * https://code.google.com/p/chromium/issues/detail?id=370951
+         */
+
+        // if (this._flagClip) {
+
+        //   clip = svg.getClip(this);
+        //   elem = this._renderer.elem;
+
+        //   if (this._clip) {
+        //     elem.removeAttribute('id');
+        //     clip.setAttribute('id', this.id);
+        //     clip.appendChild(elem);
+        //   } else {
+        //     clip.removeAttribute('id');
+        //     elem.setAttribute('id', this.id);
+        //     this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+        //   }
+
+        // }
+
+        if (this._flagMask) {
+          if (this._mask) {
+            this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+          } else {
+            this._renderer.elem.removeAttribute('clip-path');
+          }
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    path: {
+
+      render: function(domElement) {
+
+        this._update();
+
+        // Shortcut for hidden objects.
+        // Doesn't reset the flags, so changes are stored and
+        // applied once the object is visible again
+        if (this._opacity === 0 && !this._flagOpacity) {
+          return this;
+        }
+
+        // Collect any attribute that needs to be changed here
+        var changed = {};
+
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+        if (flagMatrix) {
+          changed.transform = 'matrix(' + this._matrix.toString() + ')';
+        }
+
+        if (this._flagVertices) {
+          var vertices = svg.toString(this._vertices, this._closed);
+          changed.d = vertices;
+        }
+
+        if (this._fill && this._fill._renderer) {
+          this._fill._update();
+          svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
+        }
+
+        if (this._flagFill) {
+          changed.fill = this._fill && this._fill.id
+            ? 'url(#' + this._fill.id + ')' : this._fill;
+        }
+
+        if (this._stroke && this._stroke._renderer) {
+          this._stroke._update();
+          svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
+        }
+
+        if (this._flagStroke) {
+          changed.stroke = this._stroke && this._stroke.id
+            ? 'url(#' + this._stroke.id + ')' : this._stroke;
+        }
+
+        if (this._flagLinewidth) {
+          changed['stroke-width'] = this._linewidth;
+        }
+
+        if (this._flagOpacity) {
+          changed['stroke-opacity'] = this._opacity;
+          changed['fill-opacity'] = this._opacity;
+        }
+
+        if (this._flagVisible) {
+          changed.visibility = this._visible ? 'visible' : 'hidden';
+        }
+
+        if (this._flagCap) {
+          changed['stroke-linecap'] = this._cap;
+        }
+
+        if (this._flagJoin) {
+          changed['stroke-linejoin'] = this._join;
+        }
+
+        if (this._flagMiter) {
+          changed['stroke-miterlimit'] = this._miter;
+        }
+
+        // If there is no attached DOM element yet,
+        // create it with all necessary attributes.
+        if (!this._renderer.elem) {
+
+          changed.id = this.id;
+          this._renderer.elem = svg.createElement('path', changed);
+          domElement.appendChild(this._renderer.elem);
+
+        // Otherwise apply all pending attributes
+        } else {
+          svg.setAttributes(this._renderer.elem, changed);
+        }
+
+        if (this._flagClip) {
+
+          var clip = svg.getClip(this);
+          var elem = this._renderer.elem;
+
+          if (this._clip) {
+            elem.removeAttribute('id');
+            clip.setAttribute('id', this.id);
+            clip.appendChild(elem);
+          } else {
+            clip.removeAttribute('id');
+            elem.setAttribute('id', this.id);
+            this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+          }
+
+        }
+
+        /**
+         * Commented two-way functionality of clips / masks with groups and
+         * polygons. Uncomment when this bug is fixed:
+         * https://code.google.com/p/chromium/issues/detail?id=370951
+         */
+
+        // if (this._flagMask) {
+        //   if (this._mask) {
+        //     elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+        //   } else {
+        //     elem.removeAttribute('clip-path');
+        //   }
+        // }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    text: {
+
+      render: function(domElement) {
+
+        this._update();
+
+        var changed = {};
+
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+        if (flagMatrix) {
+          changed.transform = 'matrix(' + this._matrix.toString() + ')';
+        }
+
+        if (this._flagFamily) {
+          changed['font-family'] = this._family;
+        }
+        if (this._flagSize) {
+          changed['font-size'] = this._size;
+        }
+        if (this._flagLeading) {
+          changed['line-height'] = this._leading;
+        }
+        if (this._flagAlignment) {
+          changed['text-anchor'] = svg.alignments[this._alignment] || this._alignment;
+        }
+        if (this._flagBaseline) {
+          changed['alignment-baseline'] = changed['dominant-baseline'] = this._baseline;
+        }
+        if (this._flagStyle) {
+          changed['font-style'] = this._style;
+        }
+        if (this._flagWeight) {
+          changed['font-weight'] = this._weight;
+        }
+        if (this._flagDecoration) {
+          changed['text-decoration'] = this._decoration;
+        }
+        if (this._fill && this._fill._renderer) {
+          this._fill._update();
+          svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
+        }
+        if (this._flagFill) {
+          changed.fill = this._fill && this._fill.id
+            ? 'url(#' + this._fill.id + ')' : this._fill;
+        }
+        if (this._stroke && this._stroke._renderer) {
+          this._stroke._update();
+          svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
+        }
+        if (this._flagStroke) {
+          changed.stroke = this._stroke && this._stroke.id
+            ? 'url(#' + this._stroke.id + ')' : this._stroke;
+        }
+        if (this._flagLinewidth) {
+          changed['stroke-width'] = this._linewidth;
+        }
+        if (this._flagOpacity) {
+          changed.opacity = this._opacity;
+        }
+        if (this._flagVisible) {
+          changed.visibility = this._visible ? 'visible' : 'hidden';
+        }
+
+        if (!this._renderer.elem) {
+
+          changed.id = this.id;
+
+          this._renderer.elem = svg.createElement('text', changed);
+          domElement.defs.appendChild(this._renderer.elem);
+
+        } else {
+
+          svg.setAttributes(this._renderer.elem, changed);
+
+        }
+
+        if (this._flagClip) {
+
+          var clip = svg.getClip(this);
+          var elem = this._renderer.elem;
+
+          if (this._clip) {
+            elem.removeAttribute('id');
+            clip.setAttribute('id', this.id);
+            clip.appendChild(elem);
+          } else {
+            clip.removeAttribute('id');
+            elem.setAttribute('id', this.id);
+            this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+          }
+
+        }
+
+        if (this._flagValue) {
+          this._renderer.elem.textContent = this._value;
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'linear-gradient': {
+
+      render: function(domElement, silent) {
+
+        if (!silent) {
+          this._update();
+        }
+
+        var changed = {};
+
+        if (this._flagEndPoints) {
+          changed.x1 = this.left._x;
+          changed.y1 = this.left._y;
+          changed.x2 = this.right._x;
+          changed.y2 = this.right._y;
+        }
+
+        if (this._flagSpread) {
+          changed.spreadMethod = this._spread;
+        }
+
+        // If there is no attached DOM element yet,
+        // create it with all necessary attributes.
+        if (!this._renderer.elem) {
+
+          changed.id = this.id;
+          changed.gradientUnits = 'userSpaceOnUse';
+          this._renderer.elem = svg.createElement('linearGradient', changed);
+          domElement.defs.appendChild(this._renderer.elem);
+
+        // Otherwise apply all pending attributes
+        } else {
+
+          svg.setAttributes(this._renderer.elem, changed);
+
+        }
+
+        if (this._flagStops) {
+
+          var lengthChanged = this._renderer.elem.childNodes.length
+            !== this.stops.length;
+
+          if (lengthChanged) {
+            this._renderer.elem.childNodes.length = 0;
+          }
+
+          for (var i = 0; i < this.stops.length; i++) {
+
+            var stop = this.stops[i];
+            var attrs = {};
+
+            if (stop._flagOffset) {
+              attrs.offset = 100 * stop._offset + '%';
+            }
+            if (stop._flagColor) {
+              attrs['stop-color'] = stop._color;
+            }
+            if (stop._flagOpacity) {
+              attrs['stop-opacity'] = stop._opacity;
+            }
+
+            if (!stop._renderer.elem) {
+              stop._renderer.elem = svg.createElement('stop', attrs);
+            } else {
+              svg.setAttributes(stop._renderer.elem, attrs);
+            }
+
+            if (lengthChanged) {
+              this._renderer.elem.appendChild(stop._renderer.elem);
+            }
+            stop.flagReset();
+
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'radial-gradient': {
+
+      render: function(domElement, silent) {
+
+        if (!silent) {
+          this._update();
+        }
+
+        var changed = {};
+
+        if (this._flagCenter) {
+          changed.cx = this.center._x;
+          changed.cy = this.center._y;
+        }
+        if (this._flagFocal) {
+          changed.fx = this.focal._x;
+          changed.fy = this.focal._y;
+        }
+
+        if (this._flagRadius) {
+          changed.r = this._radius;
+        }
+
+        if (this._flagSpread) {
+          changed.spreadMethod = this._spread;
+        }
+
+        // If there is no attached DOM element yet,
+        // create it with all necessary attributes.
+        if (!this._renderer.elem) {
+
+          changed.id = this.id;
+          changed.gradientUnits = 'userSpaceOnUse';
+          this._renderer.elem = svg.createElement('radialGradient', changed);
+          domElement.defs.appendChild(this._renderer.elem);
+
+        // Otherwise apply all pending attributes
+        } else {
+
+          svg.setAttributes(this._renderer.elem, changed);
+
+        }
+
+        if (this._flagStops) {
+
+          var lengthChanged = this._renderer.elem.childNodes.length
+            !== this.stops.length;
+
+          if (lengthChanged) {
+            this._renderer.elem.childNodes.length = 0;
+          }
+
+          for (var i = 0; i < this.stops.length; i++) {
+
+            var stop = this.stops[i];
+            var attrs = {};
+
+            if (stop._flagOffset) {
+              attrs.offset = 100 * stop._offset + '%';
+            }
+            if (stop._flagColor) {
+              attrs['stop-color'] = stop._color;
+            }
+            if (stop._flagOpacity) {
+              attrs['stop-opacity'] = stop._opacity;
+            }
+
+            if (!stop._renderer.elem) {
+              stop._renderer.elem = svg.createElement('stop', attrs);
+            } else {
+              svg.setAttributes(stop._renderer.elem, attrs);
+            }
+
+            if (lengthChanged) {
+              this._renderer.elem.appendChild(stop._renderer.elem);
+            }
+            stop.flagReset();
+
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    texture: {
+
+      render: function(domElement, silent) {
+
+        if (!silent) {
+          this._update();
+        }
+
+        var changed = {};
+        var styles = { x: 0, y: 0 };
+        var image = this.image;
+
+        if (this._flagLoaded && this.loaded) {
+
+          switch (image.nodeName.toLowerCase()) {
+
+            case 'canvas':
+              styles.href = styles['xlink:href'] = image.toDataURL('image/png');
+              break;
+            case 'img':
+            case 'image':
+              styles.href = styles['xlink:href'] = this.src;
+              break;
+
+          }
+
+        }
+
+        if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+          changed.x = this._offset.x;
+          changed.y = this._offset.y;
+
+          if (image) {
+
+            changed.x -= image.width / 2;
+            changed.y -= image.height / 2;
+
+            if (this._scale instanceof Two.Vector) {
+              changed.x *= this._scale.x;
+              changed.y *= this._scale.y;
+            } else {
+              changed.x *= this._scale;
+              changed.y *= this._scale;
+            }
+          }
+
+          if (changed.x > 0) {
+            changed.x *= - 1;
+          }
+          if (changed.y > 0) {
+            changed.y *= - 1;
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded || this._flagRepeat) {
+
+          changed.width = 0;
+          changed.height = 0;
+
+          if (image) {
+
+            styles.width = changed.width = image.width;
+            styles.height = changed.height = image.height;
+
+            // TODO: Hack / Bandaid
+            switch (this._repeat) {
+              case 'no-repeat':
+                changed.width += 1;
+                changed.height += 1;
+                break;
+            }
+
+            if (this._scale instanceof Two.Vector) {
+              changed.width *= this._scale.x;
+              changed.height *= this._scale.y;
+            } else {
+              changed.width *= this._scale;
+              changed.height *= this._scale;
+            }
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded) {
+          if (!this._renderer.image) {
+            this._renderer.image = svg.createElement('image', styles);
+          } else if (!_.isEmpty(styles)) {
+            svg.setAttributes(this._renderer.image, styles);
+          }
+        }
+
+        if (!this._renderer.elem) {
+
+          changed.id = this.id;
+          changed.patternUnits = 'userSpaceOnUse';
+          this._renderer.elem = svg.createElement('pattern', changed);
+          domElement.defs.appendChild(this._renderer.elem);
+
+        } else if (!_.isEmpty(changed)) {
+
+          svg.setAttributes(this._renderer.elem, changed);
+
+        }
+
+        if (this._renderer.elem && this._renderer.image && !this._renderer.appended) {
+          this._renderer.elem.appendChild(this._renderer.image);
+          this._renderer.appended = true;
+        }
+
+        return this.flagReset();
+
+      }
+
+    }
+
+  };
+
+  /**
+   * @class
+   */
+  var Renderer = Two[Two.Types.svg] = function(params) {
+
+    this.domElement = params.domElement || svg.createElement('svg');
+
+    this.scene = new Two.Group();
+    this.scene.parent = this;
+
+    this.defs = svg.createElement('defs');
+    this.domElement.appendChild(this.defs);
+    this.domElement.defs = this.defs;
+    this.domElement.style.overflow = 'hidden';
+
+  };
+
+  _.extend(Renderer, {
+
+    Utils: svg
+
+  });
+
+  _.extend(Renderer.prototype, Two.Utils.Events, {
+
+    setSize: function(width, height) {
+
+      this.width = width;
+      this.height = height;
+
+      svg.setAttributes(this.domElement, {
+        width: width,
+        height: height
+      });
+
+      return this;
+
+    },
+
+    render: function() {
+
+      svg.group.render.call(this.scene, this.domElement);
+
+      return this;
+
+    }
+
+  });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  /**
+   * Constants
+   */
+  var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
+  var getRatio = Two.Utils.getRatio;
+  var _ = Two.Utils;
+
+  // Returns true if this is a non-transforming matrix
+  var isDefaultMatrix = function (m) {
+    return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0);
+  };
+
+  var canvas = {
+
+    isHidden: /(none|transparent)/i,
+
+    alignments: {
+      left: 'start',
+      middle: 'center',
+      right: 'end'
+    },
+
+    shim: function(elem) {
+      elem.tagName = 'canvas';
+      elem.nodeType = 1;
+      return elem;
+    },
+
+    group: {
+
+      renderChild: function(child) {
+        canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
+      },
+
+      render: function(ctx) {
+
+        // TODO: Add a check here to only invoke _update if need be.
+        this._update();
+
+        var matrix = this._matrix.elements;
+        var parent = this.parent;
+        this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+        var defaultMatrix = isDefaultMatrix(matrix);
+
+        var mask = this._mask;
+        // var clip = this._clip;
+
+        if (!this._renderer.context) {
+          this._renderer.context = {};
+        }
+
+        this._renderer.context.ctx = ctx;
+        // this._renderer.context.clip = clip;
+
+        if (!defaultMatrix) {
+          ctx.save();
+          ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+        }
+
+        if (mask) {
+          canvas[mask._renderer.type].render.call(mask, ctx, true);
+        }
+
+        if (this.opacity > 0 && this.scale !== 0) {
+          for (var i = 0; i < this.children.length; i++) {
+            var child = this.children[i];
+            canvas[child._renderer.type].render.call(child, ctx);
+          }
+        }
+
+        if (!defaultMatrix) {
+          ctx.restore();
+        }
+
+       /**
+         * Commented two-way functionality of clips / masks with groups and
+         * polygons. Uncomment when this bug is fixed:
+         * https://code.google.com/p/chromium/issues/detail?id=370951
+         */
+
+        // if (clip) {
+        //   ctx.clip();
+        // }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    path: {
+
+      render: function(ctx, forced, parentClipped) {
+
+        var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter,
+            closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy,
+            ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset;
+
+        // TODO: Add a check here to only invoke _update if need be.
+        this._update();
+
+        matrix = this._matrix.elements;
+        stroke = this._stroke;
+        linewidth = this._linewidth;
+        fill = this._fill;
+        opacity = this._opacity * this.parent._renderer.opacity;
+        visible = this._visible;
+        cap = this._cap;
+        join = this._join;
+        miter = this._miter;
+        closed = this._closed;
+        commands = this._vertices; // Commands
+        length = commands.length;
+        last = length - 1;
+        defaultMatrix = isDefaultMatrix(matrix);
+
+        // mask = this._mask;
+        clip = this._clip;
+
+        if (!forced && (!visible || clip)) {
+          return this;
+        }
+
+        // Transform
+        if (!defaultMatrix) {
+          ctx.save();
+          ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+        }
+
+       /**
+         * Commented two-way functionality of clips / masks with groups and
+         * polygons. Uncomment when this bug is fixed:
+         * https://code.google.com/p/chromium/issues/detail?id=370951
+         */
+
+        // if (mask) {
+        //   canvas[mask._renderer.type].render.call(mask, ctx, true);
+        // }
+
+        // Styles
+        if (fill) {
+          if (_.isString(fill)) {
+            ctx.fillStyle = fill;
+          } else {
+            canvas[fill._renderer.type].render.call(fill, ctx);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (_.isString(stroke)) {
+            ctx.strokeStyle = stroke;
+          } else {
+            canvas[stroke._renderer.type].render.call(stroke, ctx);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+        if (miter) {
+          ctx.miterLimit = miter;
+        }
+        if (join) {
+          ctx.lineJoin = join;
+        }
+        if (cap) {
+          ctx.lineCap = cap;
+        }
+        if (_.isNumber(opacity)) {
+          ctx.globalAlpha = opacity;
+        }
+
+        ctx.beginPath();
+
+        for (var i = 0; i < commands.length; i++) {
+
+          b = commands[i];
+
+          x = toFixed(b._x);
+          y = toFixed(b._y);
+
+          switch (b._command) {
+
+            case Two.Commands.close:
+              ctx.closePath();
+              break;
+
+            case Two.Commands.curve:
+
+              prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+              next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+              a = commands[prev];
+              c = commands[next];
+              ar = (a.controls && a.controls.right) || Two.Vector.zero;
+              bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+              if (a._relative) {
+                vx = (ar.x + toFixed(a._x));
+                vy = (ar.y + toFixed(a._y));
+              } else {
+                vx = toFixed(ar.x);
+                vy = toFixed(ar.y);
+              }
+
+              if (b._relative) {
+                ux = (bl.x + toFixed(b._x));
+                uy = (bl.y + toFixed(b._y));
+              } else {
+                ux = toFixed(bl.x);
+                uy = toFixed(bl.y);
+              }
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              if (i >= last && closed) {
+
+                c = d;
+
+                br = (b.controls && b.controls.right) || Two.Vector.zero;
+                cl = (c.controls && c.controls.left) || Two.Vector.zero;
+
+                if (b._relative) {
+                  vx = (br.x + toFixed(b._x));
+                  vy = (br.y + toFixed(b._y));
+                } else {
+                  vx = toFixed(br.x);
+                  vy = toFixed(br.y);
+                }
+
+                if (c._relative) {
+                  ux = (cl.x + toFixed(c._x));
+                  uy = (cl.y + toFixed(c._y));
+                } else {
+                  ux = toFixed(cl.x);
+                  uy = toFixed(cl.y);
+                }
+
+                x = toFixed(c._x);
+                y = toFixed(c._y);
+
+                ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              }
+
+              break;
+
+            case Two.Commands.line:
+              ctx.lineTo(x, y);
+              break;
+
+            case Two.Commands.move:
+              d = b;
+              ctx.moveTo(x, y);
+              break;
+
+          }
+        }
+
+        // Loose ends
+
+        if (closed) {
+          ctx.closePath();
+        }
+
+        if (!clip && !parentClipped) {
+          if (!canvas.isHidden.test(fill)) {
+            isOffset = fill._renderer && fill._renderer.offset
+            if (isOffset) {
+              ctx.save();
+              ctx.translate(
+                - fill._renderer.offset.x, - fill._renderer.offset.y);
+              ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+            }
+            ctx.fill();
+            if (isOffset) {
+              ctx.restore();
+            }
+          }
+          if (!canvas.isHidden.test(stroke)) {
+            isOffset = stroke._renderer && stroke._renderer.offset;
+            if (isOffset) {
+              ctx.save();
+              ctx.translate(
+                - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+              ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+              ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+            }
+            ctx.stroke();
+            if (isOffset) {
+              ctx.restore();
+            }
+          }
+        }
+
+        if (!defaultMatrix) {
+          ctx.restore();
+        }
+
+        if (clip && !parentClipped) {
+          ctx.clip();
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    text: {
+
+      render: function(ctx, forced, parentClipped) {
+
+        // TODO: Add a check here to only invoke _update if need be.
+        this._update();
+
+        var matrix = this._matrix.elements;
+        var stroke = this._stroke;
+        var linewidth = this._linewidth;
+        var fill = this._fill;
+        var opacity = this._opacity * this.parent._renderer.opacity;
+        var visible = this._visible;
+        var defaultMatrix = isDefaultMatrix(matrix);
+        var isOffset = fill._renderer && fill._renderer.offset
+          && stroke._renderer && stroke._renderer.offset;
+
+        var a, b, c, d, e, sx, sy;
+
+        // mask = this._mask;
+        var clip = this._clip;
+
+        if (!forced && (!visible || clip)) {
+          return this;
+        }
+
+        // Transform
+        if (!defaultMatrix) {
+          ctx.save();
+          ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+        }
+
+       /**
+         * Commented two-way functionality of clips / masks with groups and
+         * polygons. Uncomment when this bug is fixed:
+         * https://code.google.com/p/chromium/issues/detail?id=370951
+         */
+
+        // if (mask) {
+        //   canvas[mask._renderer.type].render.call(mask, ctx, true);
+        // }
+
+        if (!isOffset) {
+          ctx.font = [this._style, this._weight, this._size + 'px/' +
+            this._leading + 'px', this._family].join(' ');
+        }
+
+        ctx.textAlign = canvas.alignments[this._alignment] || this._alignment;
+        ctx.textBaseline = this._baseline;
+
+        // Styles
+        if (fill) {
+          if (_.isString(fill)) {
+            ctx.fillStyle = fill;
+          } else {
+            canvas[fill._renderer.type].render.call(fill, ctx);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (_.isString(stroke)) {
+            ctx.strokeStyle = stroke;
+          } else {
+            canvas[stroke._renderer.type].render.call(stroke, ctx);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+        if (_.isNumber(opacity)) {
+          ctx.globalAlpha = opacity;
+        }
+
+        if (!clip && !parentClipped) {
+
+          if (!canvas.isHidden.test(fill)) {
+
+            if (fill._renderer && fill._renderer.offset) {
+
+              sx = toFixed(fill._renderer.scale.x);
+              sy = toFixed(fill._renderer.scale.y);
+
+              ctx.save();
+              ctx.translate( - toFixed(fill._renderer.offset.x),
+                - toFixed(fill._renderer.offset.y));
+              ctx.scale(sx, sy);
+
+              a = this._size / fill._renderer.scale.y;
+              b = this._leading / fill._renderer.scale.y;
+              ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
+                toFixed(b) + 'px', this._family].join(' ');
+
+              c = fill._renderer.offset.x / fill._renderer.scale.x;
+              d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+              ctx.fillText(this.value, toFixed(c), toFixed(d));
+              ctx.restore();
+
+            } else {
+              ctx.fillText(this.value, 0, 0);
+            }
+
+          }
+
+          if (!canvas.isHidden.test(stroke)) {
+
+            if (stroke._renderer && stroke._renderer.offset) {
+
+              sx = toFixed(stroke._renderer.scale.x);
+              sy = toFixed(stroke._renderer.scale.y);
+
+              ctx.save();
+              ctx.translate(- toFixed(stroke._renderer.offset.x),
+                - toFixed(stroke._renderer.offset.y));
+              ctx.scale(sx, sy);
+
+              a = this._size / stroke._renderer.scale.y;
+              b = this._leading / stroke._renderer.scale.y;
+              ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
+                toFixed(b) + 'px', this._family].join(' ');
+
+              c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+              d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+              e = linewidth / stroke._renderer.scale.x;
+
+              ctx.lineWidth = toFixed(e);
+              ctx.strokeText(this.value, toFixed(c), toFixed(d));
+              ctx.restore();
+
+            } else {
+              ctx.strokeText(this.value, 0, 0);
+            }
+          }
+        }
+
+        if (!defaultMatrix) {
+          ctx.restore();
+        }
+
+        // TODO: Test for text
+        if (clip && !parentClipped) {
+          ctx.clip();
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'linear-gradient': {
+
+      render: function(ctx) {
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+          this._renderer.effect = ctx.createLinearGradient(
+            this.left._x, this.left._y,
+            this.right._x, this.right._y
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'radial-gradient': {
+
+      render: function(ctx) {
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagCenter || this._flagFocal
+            || this._flagRadius || this._flagStops) {
+
+          this._renderer.effect = ctx.createRadialGradient(
+            this.center._x, this.center._y, 0,
+            this.focal._x, this.focal._y, this._radius
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    texture: {
+
+      render: function(ctx) {
+
+        this._update();
+
+        var image = this.image;
+        var repeat;
+
+        if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+          this._renderer.effect = ctx.createPattern(this.image, this._repeat);
+        }
+
+        if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+          if (!(this._renderer.offset instanceof Two.Vector)) {
+            this._renderer.offset = new Two.Vector();
+          }
+
+          this._renderer.offset.x = - this._offset.x;
+          this._renderer.offset.y = - this._offset.y;
+
+          if (image) {
+
+            this._renderer.offset.x += image.width / 2;
+            this._renderer.offset.y += image.height / 2;
+
+            if (this._scale instanceof Two.Vector) {
+              this._renderer.offset.x *= this._scale.x;
+              this._renderer.offset.y *= this._scale.y;
+            } else {
+              this._renderer.offset.x *= this._scale;
+              this._renderer.offset.y *= this._scale;
+            }
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded) {
+
+          if (!(this._renderer.scale instanceof Two.Vector)) {
+            this._renderer.scale = new Two.Vector();
+          }
+
+          if (this._scale instanceof Two.Vector) {
+            this._renderer.scale.copy(this._scale);
+          } else {
+            this._renderer.scale.set(this._scale, this._scale);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    }
+
+  };
+
+  var Renderer = Two[Two.Types.canvas] = function(params) {
+    // Smoothing property. Defaults to true
+    // Set it to false when working with pixel art.
+    // false can lead to better performance, since it would use a cheaper interpolation algorithm.
+    // It might not make a big difference on GPU backed canvases.
+    var smoothing = (params.smoothing !== false);
+    this.domElement = params.domElement || document.createElement('canvas');
+    this.ctx = this.domElement.getContext('2d');
+    this.overdraw = params.overdraw || false;
+
+    if (!_.isUndefined(this.ctx.imageSmoothingEnabled)) {
+      this.ctx.imageSmoothingEnabled = smoothing;
+    }
+
+    // Everything drawn on the canvas needs to be added to the scene.
+    this.scene = new Two.Group();
+    this.scene.parent = this;
+  };
+
+
+  _.extend(Renderer, {
+
+    Utils: canvas
+
+  });
+
+  _.extend(Renderer.prototype, Two.Utils.Events, {
+
+    setSize: function(width, height, ratio) {
+
+      this.width = width;
+      this.height = height;
+
+      this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
+
+      this.domElement.width = width * this.ratio;
+      this.domElement.height = height * this.ratio;
+
+      if (this.domElement.style) {
+        _.extend(this.domElement.style, {
+          width: width + 'px',
+          height: height + 'px'
+        });
+      }
+
+      return this;
+
+    },
+
+    render: function() {
+
+      var isOne = this.ratio === 1;
+
+      if (!isOne) {
+        this.ctx.save();
+        this.ctx.scale(this.ratio, this.ratio);
+      }
+
+      if (!this.overdraw) {
+        this.ctx.clearRect(0, 0, this.width, this.height);
+      }
+
+      canvas.group.render.call(this.scene, this.ctx);
+
+      if (!isOne) {
+        this.ctx.restore();
+      }
+
+      return this;
+
+    }
+
+  });
+
+  function resetTransform(ctx) {
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+  }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  /**
+   * Constants
+   */
+
+  var root = Two.root,
+    multiplyMatrix = Two.Matrix.Multiply,
+    mod = Two.Utils.mod,
+    identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
+    transformation = new Two.Array(9),
+    getRatio = Two.Utils.getRatio,
+    getComputedMatrix = Two.Utils.getComputedMatrix,
+    toFixed = Two.Utils.toFixed,
+    _ = Two.Utils;
+
+  var webgl = {
+
+    isHidden: /(none|transparent)/i,
+
+    canvas: (root.document ? root.document.createElement('canvas') : { getContext: _.identity }),
+
+    alignments: {
+      left: 'start',
+      middle: 'center',
+      right: 'end'
+    },
+
+    matrix: new Two.Matrix(),
+
+    uv: new Two.Array([
+      0, 0,
+      1, 0,
+      0, 1,
+      0, 1,
+      1, 0,
+      1, 1
+    ]),
+
+    group: {
+
+      removeChild: function(child, gl) {
+        if (child.children) {
+          for (var i = 0; i < child.children.length; i++) {
+            webgl.group.removeChild(child.children[i], gl);
+          }
+          return;
+        }
+        // Deallocate texture to free up gl memory.
+        gl.deleteTexture(child._renderer.texture);
+        delete child._renderer.texture;
+      },
+
+      renderChild: function(child) {
+        webgl[child._renderer.type].render.call(child, this.gl, this.program);
+      },
+
+      render: function(gl, program) {
+
+        this._update();
+
+        var parent = this.parent;
+        var flagParentMatrix = (parent._matrix && parent._matrix.manual) || parent._flagMatrix;
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+        if (flagParentMatrix || flagMatrix) {
+
+          if (!this._renderer.matrix) {
+            this._renderer.matrix = new Two.Array(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+          this._matrix.toArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+          this._renderer.scale = this._scale * parent._renderer.scale;
+
+          if (flagParentMatrix) {
+            this._flagMatrix = true;
+          }
+
+        }
+
+        if (this._mask) {
+
+          gl.enable(gl.STENCIL_TEST);
+          gl.stencilFunc(gl.ALWAYS, 1, 1);
+
+          gl.colorMask(false, false, false, true);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
+
+          webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+          gl.colorMask(true, true, true, true);
+          gl.stencilFunc(gl.NOTEQUAL, 0, 1);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+
+        }
+
+        this._flagOpacity = parent._flagOpacity || this._flagOpacity;
+
+        this._renderer.opacity = this._opacity
+          * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+        if (this._flagSubtractions) {
+          for (var i = 0; i < this.subtractions.length; i++) {
+            webgl.group.removeChild(this.subtractions[i], gl);
+          }
+        }
+
+        this.children.forEach(webgl.group.renderChild, {
+          gl: gl,
+          program: program
+        });
+
+        if (this._mask) {
+
+          gl.colorMask(false, false, false, false);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
+
+          webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+          gl.colorMask(true, true, true, true);
+          gl.stencilFunc(gl.NOTEQUAL, 0, 1);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+
+          gl.disable(gl.STENCIL_TEST);
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    path: {
+
+      updateCanvas: function(elem) {
+
+        var next, prev, a, c, ux, uy, vx, vy, ar, bl, br, cl, x, y;
+        var isOffset;
+
+        var commands = elem._vertices;
+        var canvas = this.canvas;
+        var ctx = this.ctx;
+
+        // Styles
+        var scale = elem._renderer.scale;
+        var stroke = elem._stroke;
+        var linewidth = elem._linewidth;
+        var fill = elem._fill;
+        var opacity = elem._renderer.opacity || elem._opacity;
+        var cap = elem._cap;
+        var join = elem._join;
+        var miter = elem._miter;
+        var closed = elem._closed;
+        var length = commands.length;
+        var last = length - 1;
+
+        canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
+        canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
+
+        var centroid = elem._renderer.rect.centroid;
+        var cx = centroid.x;
+        var cy = centroid.y;
+
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+        if (fill) {
+          if (_.isString(fill)) {
+            ctx.fillStyle = fill;
+          } else {
+            webgl[fill._renderer.type].render.call(fill, ctx, elem);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (_.isString(stroke)) {
+            ctx.strokeStyle = stroke;
+          } else {
+            webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+        if (miter) {
+          ctx.miterLimit = miter;
+        }
+        if (join) {
+          ctx.lineJoin = join;
+        }
+        if (cap) {
+          ctx.lineCap = cap;
+        }
+        if (_.isNumber(opacity)) {
+          ctx.globalAlpha = opacity;
+        }
+
+        var d;
+        ctx.save();
+        ctx.scale(scale, scale);
+        ctx.translate(cx, cy);
+
+        ctx.beginPath();
+        for (var i = 0; i < commands.length; i++) {
+
+          b = commands[i];
+
+          x = toFixed(b._x);
+          y = toFixed(b._y);
+
+          switch (b._command) {
+
+            case Two.Commands.close:
+              ctx.closePath();
+              break;
+
+            case Two.Commands.curve:
+
+              prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+              next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+              a = commands[prev];
+              c = commands[next];
+              ar = (a.controls && a.controls.right) || Two.Vector.zero;
+              bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+              if (a._relative) {
+                vx = toFixed((ar.x + a._x));
+                vy = toFixed((ar.y + a._y));
+              } else {
+                vx = toFixed(ar.x);
+                vy = toFixed(ar.y);
+              }
+
+              if (b._relative) {
+                ux = toFixed((bl.x + b._x));
+                uy = toFixed((bl.y + b._y));
+              } else {
+                ux = toFixed(bl.x);
+                uy = toFixed(bl.y);
+              }
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              if (i >= last && closed) {
+
+                c = d;
+
+                br = (b.controls && b.controls.right) || Two.Vector.zero;
+                cl = (c.controls && c.controls.left) || Two.Vector.zero;
+
+                if (b._relative) {
+                  vx = toFixed((br.x + b._x));
+                  vy = toFixed((br.y + b._y));
+                } else {
+                  vx = toFixed(br.x);
+                  vy = toFixed(br.y);
+                }
+
+                if (c._relative) {
+                  ux = toFixed((cl.x + c._x));
+                  uy = toFixed((cl.y + c._y));
+                } else {
+                  ux = toFixed(cl.x);
+                  uy = toFixed(cl.y);
+                }
+
+                x = toFixed(c._x);
+                y = toFixed(c._y);
+
+                ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              }
+
+              break;
+
+            case Two.Commands.line:
+              ctx.lineTo(x, y);
+              break;
+
+            case Two.Commands.move:
+              d = b;
+              ctx.moveTo(x, y);
+              break;
+
+          }
+
+        }
+
+        // Loose ends
+
+        if (closed) {
+          ctx.closePath();
+        }
+
+        if (!webgl.isHidden.test(fill)) {
+          isOffset = fill._renderer && fill._renderer.offset
+          if (isOffset) {
+            ctx.save();
+            ctx.translate(
+              - fill._renderer.offset.x, - fill._renderer.offset.y);
+            ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+          }
+          ctx.fill();
+          if (isOffset) {
+            ctx.restore();
+          }
+        }
+
+        if (!webgl.isHidden.test(stroke)) {
+          isOffset = stroke._renderer && stroke._renderer.offset;
+          if (isOffset) {
+            ctx.save();
+            ctx.translate(
+              - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+            ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+            ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+          }
+          ctx.stroke();
+          if (isOffset) {
+            ctx.restore();
+          }
+        }
+
+        ctx.restore();
+
+      },
+
+      /**
+       * Returns the rect of a set of verts. Typically takes vertices that are
+       * "centered" around 0 and returns them to be anchored upper-left.
+       */
+      getBoundingClientRect: function(vertices, border, rect) {
+
+        var left = Infinity, right = -Infinity,
+            top = Infinity, bottom = -Infinity,
+            width, height;
+
+        vertices.forEach(function(v) {
+
+          var x = v.x, y = v.y, controls = v.controls;
+          var a, b, c, d, cl, cr;
+
+          top = Math.min(y, top);
+          left = Math.min(x, left);
+          right = Math.max(x, right);
+          bottom = Math.max(y, bottom);
+
+          if (!v.controls) {
+            return;
+          }
+
+          cl = controls.left;
+          cr = controls.right;
+
+          if (!cl || !cr) {
+            return;
+          }
+
+          a = v._relative ? cl.x + x : cl.x;
+          b = v._relative ? cl.y + y : cl.y;
+          c = v._relative ? cr.x + x : cr.x;
+          d = v._relative ? cr.y + y : cr.y;
+
+          if (!a || !b || !c || !d) {
+            return;
+          }
+
+          top = Math.min(b, d, top);
+          left = Math.min(a, c, left);
+          right = Math.max(a, c, right);
+          bottom = Math.max(b, d, bottom);
+
+        });
+
+        // Expand borders
+
+        if (_.isNumber(border)) {
+          top -= border;
+          left -= border;
+          right += border;
+          bottom += border;
+        }
+
+        width = right - left;
+        height = bottom - top;
+
+        rect.top = top;
+        rect.left = left;
+        rect.right = right;
+        rect.bottom = bottom;
+        rect.width = width;
+        rect.height = height;
+
+        if (!rect.centroid) {
+          rect.centroid = {};
+        }
+
+        rect.centroid.x = - left;
+        rect.centroid.y = - top;
+
+      },
+
+      render: function(gl, program, forcedParent) {
+
+        if (!this._visible || !this._opacity) {
+          return this;
+        }
+
+        this._update();
+
+        // Calculate what changed
+
+        var parent = this.parent;
+        var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+        var flagTexture = this._flagVertices || this._flagFill
+          || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+          || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+          || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagOffset || this._fill._flagScale))
+          || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+          || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+          || (this._stroke instanceof Two.Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagOffset || this._fill._flagScale))
+          || this._flagStroke || this._flagLinewidth || this._flagOpacity
+          || parent._flagOpacity || this._flagVisible || this._flagCap
+          || this._flagJoin || this._flagMiter || this._flagScale
+          || !this._renderer.texture;
+
+        if (flagParentMatrix || flagMatrix) {
+
+          if (!this._renderer.matrix) {
+            this._renderer.matrix = new Two.Array(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+
+          this._matrix.toArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+          this._renderer.scale = this._scale * parent._renderer.scale;
+
+        }
+
+        if (flagTexture) {
+
+          if (!this._renderer.rect) {
+            this._renderer.rect = {};
+          }
+
+          if (!this._renderer.triangles) {
+            this._renderer.triangles = new Two.Array(12);
+          }
+
+          this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+          webgl.path.getBoundingClientRect(this._vertices, this._linewidth, this._renderer.rect);
+          webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
+
+          webgl.updateBuffer.call(webgl, gl, this, program);
+          webgl.updateTexture.call(webgl, gl, this);
+
+        }
+
+        // if (this._mask) {
+        //   webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
+        // }
+
+        if (this._clip && !forcedParent) {
+          return;
+        }
+
+        // Draw Texture
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
+
+        gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
+
+        gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+
+        // Draw Rect
+
+        gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
+
+        gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
+
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    text: {
+
+      updateCanvas: function(elem) {
+
+        var canvas = this.canvas;
+        var ctx = this.ctx;
+
+        // Styles
+        var scale = elem._renderer.scale;
+        var stroke = elem._stroke;
+        var linewidth = elem._linewidth * scale;
+        var fill = elem._fill;
+        var opacity = elem._renderer.opacity || elem._opacity;
+
+        canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
+        canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
+
+        var centroid = elem._renderer.rect.centroid;
+        var cx = centroid.x;
+        var cy = centroid.y;
+
+        var a, b, c, d, e, sx, sy;
+        var isOffset = fill._renderer && fill._renderer.offset
+          && stroke._renderer && stroke._renderer.offset;
+
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+        if (!isOffset) {
+          ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
+            elem._leading + 'px', elem._family].join(' ');
+        }
+
+        ctx.textAlign = 'center';
+        ctx.textBaseline = 'middle';
+
+        // Styles
+        if (fill) {
+          if (_.isString(fill)) {
+            ctx.fillStyle = fill;
+          } else {
+            webgl[fill._renderer.type].render.call(fill, ctx, elem);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (_.isString(stroke)) {
+            ctx.strokeStyle = stroke;
+          } else {
+            webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+        if (_.isNumber(opacity)) {
+          ctx.globalAlpha = opacity;
+        }
+
+        ctx.save();
+        ctx.scale(scale, scale);
+        ctx.translate(cx, cy);
+
+        if (!webgl.isHidden.test(fill)) {
+
+          if (fill._renderer && fill._renderer.offset) {
+
+            sx = toFixed(fill._renderer.scale.x);
+            sy = toFixed(fill._renderer.scale.y);
+
+            ctx.save();
+            ctx.translate( - toFixed(fill._renderer.offset.x),
+              - toFixed(fill._renderer.offset.y));
+            ctx.scale(sx, sy);
+
+            a = elem._size / fill._renderer.scale.y;
+            b = elem._leading / fill._renderer.scale.y;
+            ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
+              toFixed(b) + 'px', elem._family].join(' ');
+
+            c = fill._renderer.offset.x / fill._renderer.scale.x;
+            d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+            ctx.fillText(elem.value, toFixed(c), toFixed(d));
+            ctx.restore();
+
+          } else {
+            ctx.fillText(elem.value, 0, 0);
+          }
+
+        }
+
+        if (!webgl.isHidden.test(stroke)) {
+
+          if (stroke._renderer && stroke._renderer.offset) {
+
+            sx = toFixed(stroke._renderer.scale.x);
+            sy = toFixed(stroke._renderer.scale.y);
+
+            ctx.save();
+            ctx.translate(- toFixed(stroke._renderer.offset.x),
+              - toFixed(stroke._renderer.offset.y));
+            ctx.scale(sx, sy);
+
+            a = elem._size / stroke._renderer.scale.y;
+            b = elem._leading / stroke._renderer.scale.y;
+            ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
+              toFixed(b) + 'px', elem._family].join(' ');
+
+            c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+            d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+            e = linewidth / stroke._renderer.scale.x;
+
+            ctx.lineWidth = toFixed(e);
+            ctx.strokeText(elem.value, toFixed(c), toFixed(d));
+            ctx.restore();
+
+          } else {
+            ctx.strokeText(elem.value, 0, 0);
+          }
+
+        }
+
+        ctx.restore();
+
+      },
+
+      getBoundingClientRect: function(elem, rect) {
+
+        var ctx = webgl.ctx;
+
+        ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
+          elem._leading + 'px', elem._family].join(' ');
+
+        ctx.textAlign = 'center';
+        ctx.textBaseline = elem._baseline;
+
+        // TODO: Estimate this better
+        var width = ctx.measureText(elem._value).width;
+        var height = Math.max(elem._size || elem._leading);
+
+        if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
+          // width += this._linewidth; // TODO: Not sure if the `measure` calcs this.
+          height += this._linewidth;
+        }
+
+        var w = width / 2;
+        var h = height / 2;
+
+        switch (webgl.alignments[elem._alignment] || elem._alignment) {
+
+          case webgl.alignments.left:
+            rect.left = 0;
+            rect.right = width;
+            break;
+          case webgl.alignments.right:
+            rect.left = - width;
+            rect.right = 0;
+            break;
+          default:
+            rect.left = - w;
+            rect.right = w;
+        }
+
+        // TODO: Gradients aren't inherited...
+        switch (elem._baseline) {
+          case 'bottom':
+            rect.top = - height;
+            rect.bottom = 0;
+            break;
+          case 'top':
+            rect.top = 0;
+            rect.bottom = height;
+            break;
+          default:
+            rect.top = - h;
+            rect.bottom = h;
+        }
+
+        rect.width = width;
+        rect.height = height;
+
+        if (!rect.centroid) {
+          rect.centroid = {};
+        }
+
+        // TODO:
+        rect.centroid.x = w;
+        rect.centroid.y = h;
+
+      },
+
+      render: function(gl, program, forcedParent) {
+
+        if (!this._visible || !this._opacity) {
+          return this;
+        }
+
+        this._update();
+
+        // Calculate what changed
+
+        var parent = this.parent;
+        var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+        var flagTexture = this._flagVertices || this._flagFill
+          || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+          || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+          || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded))
+          || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+          || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+          || (this._texture instanceof Two.Texture && (this._texture._flagLoaded && this._texture.loaded))
+          || this._flagStroke || this._flagLinewidth || this._flagOpacity
+          || parent._flagOpacity || this._flagVisible || this._flagScale
+          || this._flagValue || this._flagFamily || this._flagSize
+          || this._flagLeading || this._flagAlignment || this._flagBaseline
+          || this._flagStyle || this._flagWeight || this._flagDecoration
+          || !this._renderer.texture;
+
+        if (flagParentMatrix || flagMatrix) {
+
+          if (!this._renderer.matrix) {
+            this._renderer.matrix = new Two.Array(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+
+          this._matrix.toArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+          this._renderer.scale = this._scale * parent._renderer.scale;
+
+        }
+
+        if (flagTexture) {
+
+          if (!this._renderer.rect) {
+            this._renderer.rect = {};
+          }
+
+          if (!this._renderer.triangles) {
+            this._renderer.triangles = new Two.Array(12);
+          }
+
+          this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+          webgl.text.getBoundingClientRect(this, this._renderer.rect);
+          webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
+
+          webgl.updateBuffer.call(webgl, gl, this, program);
+          webgl.updateTexture.call(webgl, gl, this);
+
+        }
+
+        // if (this._mask) {
+        //   webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
+        // }
+
+        if (this._clip && !forcedParent) {
+          return;
+        }
+
+        // Draw Texture
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
+
+        gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
+
+        gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+
+        // Draw Rect
+
+        gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+
+        gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
+
+        gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
+
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'linear-gradient': {
+
+      render: function(ctx, elem) {
+
+        if (!ctx.canvas.getContext('2d')) {
+          return;
+        }
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+          this._renderer.effect = ctx.createLinearGradient(
+            this.left._x, this.left._y,
+            this.right._x, this.right._y
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'radial-gradient': {
+
+      render: function(ctx, elem) {
+
+        if (!ctx.canvas.getContext('2d')) {
+          return;
+        }
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagCenter || this._flagFocal
+            || this._flagRadius || this._flagStops) {
+
+          this._renderer.effect = ctx.createRadialGradient(
+            this.center._x, this.center._y, 0,
+            this.focal._x, this.focal._y, this._radius
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    texture: {
+
+      render: function(ctx, elem) {
+
+        if (!ctx.canvas.getContext('2d')) {
+          return;
+        }
+
+        this._update();
+
+        var image = this.image;
+        var repeat;
+
+        if (!this._renderer.effect || ((this._flagLoaded || this._flagRepeat) && this.loaded)) {
+          this._renderer.effect = ctx.createPattern(image, this._repeat);
+        }
+
+        if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+          if (!(this._renderer.offset instanceof Two.Vector)) {
+            this._renderer.offset = new Two.Vector();
+          }
+
+          this._renderer.offset.x = this._offset.x;
+          this._renderer.offset.y = this._offset.y;
+
+          if (image) {
+
+            this._renderer.offset.x -= image.width / 2;
+            this._renderer.offset.y += image.height / 2;
+
+            if (this._scale instanceof Two.Vector) {
+              this._renderer.offset.x *= this._scale.x;
+              this._renderer.offset.y *= this._scale.y;
+            } else {
+              this._renderer.offset.x *= this._scale;
+              this._renderer.offset.y *= this._scale;
+            }
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded) {
+
+          if (!(this._renderer.scale instanceof Two.Vector)) {
+            this._renderer.scale = new Two.Vector();
+          }
+
+          if (this._scale instanceof Two.Vector) {
+            this._renderer.scale.copy(this._scale);
+          } else {
+            this._renderer.scale.set(this._scale, this._scale);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    getTriangles: function(rect, triangles) {
+
+      var top = rect.top,
+          left = rect.left,
+          right = rect.right,
+          bottom = rect.bottom;
+
+      // First Triangle
+
+      triangles[0] = left;
+      triangles[1] = top;
+
+      triangles[2] = right;
+      triangles[3] = top;
+
+      triangles[4] = left;
+      triangles[5] = bottom;
+
+      // Second Triangle
+
+      triangles[6] = left;
+      triangles[7] = bottom;
+
+      triangles[8] = right;
+      triangles[9] = top;
+
+      triangles[10] = right;
+      triangles[11] = bottom;
+
+    },
+
+    updateTexture: function(gl, elem) {
+
+      this[elem._renderer.type].updateCanvas.call(webgl, elem);
+
+      if (elem._renderer.texture) {
+        gl.deleteTexture(elem._renderer.texture);
+      }
+
+      gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
+
+      // TODO: Is this necessary every time or can we do once?
+      // TODO: Create a registry for textures
+      elem._renderer.texture = gl.createTexture();
+      gl.bindTexture(gl.TEXTURE_2D, elem._renderer.texture);
+
+      // Set the parameters so we can render any size image.
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+      // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+      // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+      // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+
+      if (this.canvas.width <= 0 || this.canvas.height <= 0) {
+        return;
+      }
+
+      // Upload the image into the texture.
+      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas);
+
+    },
+
+    updateBuffer: function(gl, elem, program) {
+
+      if (_.isObject(elem._renderer.buffer)) {
+        gl.deleteBuffer(elem._renderer.buffer);
+      }
+
+      elem._renderer.buffer = gl.createBuffer();
+
+      gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.buffer);
+      gl.enableVertexAttribArray(program.position);
+
+      gl.bufferData(gl.ARRAY_BUFFER, elem._renderer.triangles, gl.STATIC_DRAW);
+
+      if (_.isObject(elem._renderer.textureCoordsBuffer)) {
+        gl.deleteBuffer(elem._renderer.textureCoordsBuffer);
+      }
+
+      elem._renderer.textureCoordsBuffer = gl.createBuffer();
+
+      gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
+      gl.enableVertexAttribArray(program.textureCoords);
+
+      gl.bufferData(gl.ARRAY_BUFFER, this.uv, gl.STATIC_DRAW);
+
+    },
+
+    program: {
+
+      create: function(gl, shaders) {
+        var program, linked, error;
+        program = gl.createProgram();
+        _.each(shaders, function(s) {
+          gl.attachShader(program, s);
+        });
+
+        gl.linkProgram(program);
+        linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+        if (!linked) {
+          error = gl.getProgramInfoLog(program);
+          gl.deleteProgram(program);
+          throw new Two.Utils.Error('unable to link program: ' + error);
+        }
+
+        return program;
+
+      }
+
+    },
+
+    shaders: {
+
+      create: function(gl, source, type) {
+        var shader, compiled, error;
+        shader = gl.createShader(gl[type]);
+        gl.shaderSource(shader, source);
+        gl.compileShader(shader);
+
+        compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+        if (!compiled) {
+          error = gl.getShaderInfoLog(shader);
+          gl.deleteShader(shader);
+          throw new Two.Utils.Error('unable to compile shader ' + shader + ': ' + error);
+        }
+
+        return shader;
+
+      },
+
+      types: {
+        vertex: 'VERTEX_SHADER',
+        fragment: 'FRAGMENT_SHADER'
+      },
+
+      vertex: [
+        'attribute vec2 a_position;',
+        'attribute vec2 a_textureCoords;',
+        '',
+        'uniform mat3 u_matrix;',
+        'uniform vec2 u_resolution;',
+        '',
+        'varying vec2 v_textureCoords;',
+        '',
+        'void main() {',
+        '   vec2 projected = (u_matrix * vec3(a_position, 1.0)).xy;',
+        '   vec2 normal = projected / u_resolution;',
+        '   vec2 clipspace = (normal * 2.0) - 1.0;',
+        '',
+        '   gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);',
+        '   v_textureCoords = a_textureCoords;',
+        '}'
+      ].join('\n'),
+
+      fragment: [
+        'precision mediump float;',
+        '',
+        'uniform sampler2D u_image;',
+        'varying vec2 v_textureCoords;',
+        '',
+        'void main() {',
+        '  gl_FragColor = texture2D(u_image, v_textureCoords);',
+        '}'
+      ].join('\n')
+
+    },
+
+    TextureRegistry: new Two.Registry()
+
+  };
+
+  webgl.ctx = webgl.canvas.getContext('2d');
+
+  var Renderer = Two[Two.Types.webgl] = function(options) {
+
+    var params, gl, vs, fs;
+    this.domElement = options.domElement || document.createElement('canvas');
+
+    // Everything drawn on the canvas needs to come from the stage.
+    this.scene = new Two.Group();
+    this.scene.parent = this;
+
+    this._renderer = {
+      matrix: new Two.Array(identity),
+      scale: 1,
+      opacity: 1
+    };
+    this._flagMatrix = true;
+
+    // http://games.greggman.com/game/webgl-and-alpha/
+    // http://www.khronos.org/registry/webgl/specs/latest/#5.2
+    params = _.defaults(options || {}, {
+      antialias: false,
+      alpha: true,
+      premultipliedAlpha: true,
+      stencil: true,
+      preserveDrawingBuffer: true,
+      overdraw: false
+    });
+
+    this.overdraw = params.overdraw;
+
+    gl = this.ctx = this.domElement.getContext('webgl', params) ||
+      this.domElement.getContext('experimental-webgl', params);
+
+    if (!this.ctx) {
+      throw new Two.Utils.Error(
+        'unable to create a webgl context. Try using another renderer.');
+    }
+
+    // Compile Base Shaders to draw in pixel space.
+    vs = webgl.shaders.create(
+      gl, webgl.shaders.vertex, webgl.shaders.types.vertex);
+    fs = webgl.shaders.create(
+      gl, webgl.shaders.fragment, webgl.shaders.types.fragment);
+
+    this.program = webgl.program.create(gl, [vs, fs]);
+    gl.useProgram(this.program);
+
+    // Create and bind the drawing buffer
+
+    // look up where the vertex data needs to go.
+    this.program.position = gl.getAttribLocation(this.program, 'a_position');
+    this.program.matrix = gl.getUniformLocation(this.program, 'u_matrix');
+    this.program.textureCoords = gl.getAttribLocation(this.program, 'a_textureCoords');
+
+    // Copied from Three.js WebGLRenderer
+    gl.disable(gl.DEPTH_TEST);
+
+    // Setup some initial statements of the gl context
+    gl.enable(gl.BLEND);
+
+    // https://code.google.com/p/chromium/issues/detail?id=316393
+    // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, gl.TRUE);
+
+    gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
+    gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
+      gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
+
+  };
+
+  _.extend(Renderer, {
+
+    Utils: webgl
+
+  });
+
+  _.extend(Renderer.prototype, Two.Utils.Events, {
+
+    setSize: function(width, height, ratio) {
+
+      this.width = width;
+      this.height = height;
+
+      this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
+
+      this.domElement.width = width * this.ratio;
+      this.domElement.height = height * this.ratio;
+
+      _.extend(this.domElement.style, {
+        width: width + 'px',
+        height: height + 'px'
+      });
+
+      width *= this.ratio;
+      height *= this.ratio;
+
+      // Set for this.stage parent scaling to account for HDPI
+      this._renderer.matrix[0] = this._renderer.matrix[4] = this._renderer.scale = this.ratio;
+
+      this._flagMatrix = true;
+
+      this.ctx.viewport(0, 0, width, height);
+
+      var resolutionLocation = this.ctx.getUniformLocation(
+        this.program, 'u_resolution');
+      this.ctx.uniform2f(resolutionLocation, width, height);
+
+      return this;
+
+    },
+
+    render: function() {
+
+      var gl = this.ctx;
+
+      if (!this.overdraw) {
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+      }
+
+      webgl.group.render.call(this.scene, gl, this.program);
+      this._flagMatrix = false;
+
+      return this;
+
+    }
+
+  });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var Shape = Two.Shape = function() {
+
+    // Private object for renderer specific variables.
+    this._renderer = {};
+    this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this);
+    this.isShape = true;
+
+    this.id = Two.Identifier + Two.uniqueId();
+    this.classList = [];
+
+    // Define matrix properties which all inherited
+    // objects of Shape have.
+
+    this._matrix = new Two.Matrix();
+
+    this.translation = new Two.Vector();
+    this.rotation = 0;
+    this.scale = 1;
+
+  };
+
+  _.extend(Shape, {
+
+    FlagMatrix: function() {
+      this._flagMatrix = true;
+    },
+
+    MakeObservable: function(object) {
+
+      Object.defineProperty(object, 'translation', {
+        enumerable: true,
+        get: function() {
+          return this._translation;
+        },
+        set: function(v) {
+          if (this._translation) {
+            this._translation.unbind(Two.Events.change, this._renderer.flagMatrix);
+          }
+          this._translation = v;
+          this._translation.bind(Two.Events.change, this._renderer.flagMatrix);
+          Shape.FlagMatrix.call(this);
+        }
+      });
+
+      Object.defineProperty(object, 'rotation', {
+        enumerable: true,
+        get: function() {
+          return this._rotation;
+        },
+        set: function(v) {
+          this._rotation = v;
+          this._flagMatrix = true;
+        }
+      });
+
+      Object.defineProperty(object, 'scale', {
+        enumerable: true,
+        get: function() {
+          return this._scale;
+        },
+        set: function(v) {
+
+          if (this._scale instanceof Two.Vector) {
+            this._scale.unbind(Two.Events.change, this._renderer.flagMatrix);
+          }
+
+          this._scale = v;
+
+          if (this._scale instanceof Two.Vector) {
+            this._scale.bind(Two.Events.change, this._renderer.flagMatrix);
+          }
+
+          this._flagMatrix = true;
+          this._flagScale = true;
+
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Shape.prototype, Two.Utils.Events, {
+
+    // Flags
+
+    _flagMatrix: true,
+    _flagScale: false,
+
+    // _flagMask: false,
+    // _flagClip: false,
+
+    // Underlying Properties
+
+    _rotation: 0,
+    _scale: 1,
+    _translation: null,
+
+    // _mask: null,
+    // _clip: false,
+
+    addTo: function(group) {
+      group.add(this);
+      return this;
+    },
+
+    clone: function() {
+      var clone = new Shape();
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      _.each(Shape.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+      return clone._update();
+    },
+
+    /**
+     * To be called before render that calculates and collates all information
+     * to be as up-to-date as possible for the render. Called once a frame.
+     */
+    _update: function(deep) {
+
+      if (!this._matrix.manual && this._flagMatrix) {
+
+        this._matrix
+          .identity()
+          .translate(this.translation.x, this.translation.y);
+
+          if (this._scale instanceof Two.Vector) {
+            this._matrix.scale(this._scale.x, this._scale.y);
+          } else {
+            this._matrix.scale(this._scale);
+          }
+
+          this._matrix.rotate(this.rotation);
+
+      }
+
+      if (deep) {
+        // Bubble up to parents mainly for `getBoundingClientRect` method.
+        if (this.parent && this.parent._update) {
+          this.parent._update();
+        }
+      }
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagMatrix = this._flagScale = false;
+
+      return this;
+
+    }
+
+  });
+
+  Shape.MakeObservable(Shape.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  /**
+   * Constants
+   */
+
+  var min = Math.min, max = Math.max, round = Math.round,
+    getComputedMatrix = Two.Utils.getComputedMatrix;
+
+  var commands = {};
+  var _ = Two.Utils;
+
+  _.each(Two.Commands, function(v, k) {
+    commands[k] = new RegExp(v);
+  });
+
+  var Path = Two.Path = function(vertices, closed, curved, manual) {
+
+    Two.Shape.call(this);
+
+    this._renderer.type = 'path';
+    this._renderer.flagVertices = _.bind(Path.FlagVertices, this);
+    this._renderer.bindVertices = _.bind(Path.BindVertices, this);
+    this._renderer.unbindVertices = _.bind(Path.UnbindVertices, this);
+
+    this._renderer.flagFill = _.bind(Path.FlagFill, this);
+    this._renderer.flagStroke = _.bind(Path.FlagStroke, this);
+
+    this._closed = !!closed;
+    this._curved = !!curved;
+
+    this.beginning = 0;
+    this.ending = 1;
+
+    // Style properties
+
+    this.fill = '#fff';
+    this.stroke = '#000';
+    this.linewidth = 1.0;
+    this.opacity = 1.0;
+    this.visible = true;
+
+    this.cap = 'butt';      // Default of Adobe Illustrator
+    this.join = 'miter';    // Default of Adobe Illustrator
+    this.miter = 4;         // Default of Adobe Illustrator
+
+    this._vertices = [];
+    this.vertices = vertices;
+
+    // Determines whether or not two.js should calculate curves, lines, and
+    // commands automatically for you or to let the developer manipulate them
+    // for themselves.
+    this.automatic = !manual;
+
+  };
+
+  _.extend(Path, {
+
+    Properties: [
+      'fill',
+      'stroke',
+      'linewidth',
+      'opacity',
+      'visible',
+      'cap',
+      'join',
+      'miter',
+
+      'closed',
+      'curved',
+      'automatic',
+      'beginning',
+      'ending'
+    ],
+
+    FlagVertices: function() {
+      this._flagVertices = true;
+      this._flagLength = true;
+    },
+
+    BindVertices: function(items) {
+
+      // This function is called a lot
+      // when importing a large SVG
+      var i = items.length;
+      while (i--) {
+        items[i].bind(Two.Events.change, this._renderer.flagVertices);
+      }
+
+      this._renderer.flagVertices();
+
+    },
+
+    UnbindVertices: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].unbind(Two.Events.change, this._renderer.flagVertices);
+      }
+
+      this._renderer.flagVertices();
+
+    },
+
+    FlagFill: function() {
+      this._flagFill = true;
+    },
+
+    FlagStroke: function() {
+      this._flagStroke = true;
+    },
+
+    MakeObservable: function(object) {
+
+      Two.Shape.MakeObservable(object);
+
+      // Only the 6 defined properties are flagged like this. The subsequent
+      // properties behave differently and need to be hand written.
+      _.each(Path.Properties.slice(2, 8), Two.Utils.defineProperty, object);
+
+      Object.defineProperty(object, 'fill', {
+        enumerable: true,
+        get: function() {
+          return this._fill;
+        },
+        set: function(f) {
+
+          if (this._fill instanceof Two.Gradient
+            || this._fill instanceof Two.LinearGradient
+            || this._fill instanceof Two.RadialGradient
+            || this._fill instanceof Two.Texture) {
+            this._fill.unbind(Two.Events.change, this._renderer.flagFill);
+          }
+
+          this._fill = f;
+          this._flagFill = true;
+
+          if (this._fill instanceof Two.Gradient
+            || this._fill instanceof Two.LinearGradient
+            || this._fill instanceof Two.RadialGradient
+            || this._fill instanceof Two.Texture) {
+            this._fill.bind(Two.Events.change, this._renderer.flagFill);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'stroke', {
+        enumerable: true,
+        get: function() {
+          return this._stroke;
+        },
+        set: function(f) {
+
+          if (this._stroke instanceof Two.Gradient
+            || this._stroke instanceof Two.LinearGradient
+            || this._stroke instanceof Two.RadialGradient
+            || this._stroke instanceof Two.Texture) {
+            this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
+          }
+
+          this._stroke = f;
+          this._flagStroke = true;
+
+          if (this._stroke instanceof Two.Gradient
+            || this._stroke instanceof Two.LinearGradient
+            || this._stroke instanceof Two.RadialGradient
+            || this._stroke instanceof Two.Texture) {
+            this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'length', {
+        get: function() {
+          if (this._flagLength) {
+            this._updateLength();
+          }
+          return this._length;
+        }
+      });
+
+      Object.defineProperty(object, 'closed', {
+        enumerable: true,
+        get: function() {
+          return this._closed;
+        },
+        set: function(v) {
+          this._closed = !!v;
+          this._flagVertices = true;
+        }
+      });
+
+      Object.defineProperty(object, 'curved', {
+        enumerable: true,
+        get: function() {
+          return this._curved;
+        },
+        set: function(v) {
+          this._curved = !!v;
+          this._flagVertices = true;
+        }
+      });
+
+      Object.defineProperty(object, 'automatic', {
+        enumerable: true,
+        get: function() {
+          return this._automatic;
+        },
+        set: function(v) {
+          if (v === this._automatic) {
+            return;
+          }
+          this._automatic = !!v;
+          var method = this._automatic ? 'ignore' : 'listen';
+          _.each(this.vertices, function(v) {
+            v[method]();
+          });
+        }
+      });
+
+      Object.defineProperty(object, 'beginning', {
+        enumerable: true,
+        get: function() {
+          return this._beginning;
+        },
+        set: function(v) {
+          this._beginning = v;
+          this._flagVertices = true;
+        }
+      });
+
+      Object.defineProperty(object, 'ending', {
+        enumerable: true,
+        get: function() {
+          return this._ending;
+        },
+        set: function(v) {
+          this._ending = v;
+          this._flagVertices = true;
+        }
+      });
+
+      Object.defineProperty(object, 'vertices', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._collection;
+        },
+
+        set: function(vertices) {
+
+          var updateVertices = this._renderer.flagVertices;
+          var bindVertices = this._renderer.bindVertices;
+          var unbindVertices = this._renderer.unbindVertices;
+
+          // Remove previous listeners
+          if (this._collection) {
+            this._collection
+              .unbind(Two.Events.insert, bindVertices)
+              .unbind(Two.Events.remove, unbindVertices);
+          }
+
+          // Create new Collection with copy of vertices
+          this._collection = new Two.Utils.Collection((vertices || []).slice(0));
+
+          // Listen for Collection changes and bind / unbind
+          this._collection
+            .bind(Two.Events.insert, bindVertices)
+            .bind(Two.Events.remove, unbindVertices);
+
+          // Bind Initial Vertices
+          bindVertices(this._collection);
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'clip', {
+        enumerable: true,
+        get: function() {
+          return this._clip;
+        },
+        set: function(v) {
+          this._clip = v;
+          this._flagClip = true;
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Path.prototype, Two.Shape.prototype, {
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    _flagVertices: true,
+    _flagLength: true,
+
+    _flagFill: true,
+    _flagStroke: true,
+    _flagLinewidth: true,
+    _flagOpacity: true,
+    _flagVisible: true,
+
+    _flagCap: true,
+    _flagJoin: true,
+    _flagMiter: true,
+
+    _flagClip: false,
+
+    // Underlying Properties
+
+    _length: 0,
+
+    _fill: '#fff',
+    _stroke: '#000',
+    _linewidth: 1.0,
+    _opacity: 1.0,
+    _visible: true,
+
+    _cap: 'round',
+    _join: 'round',
+    _miter: 4,
+
+    _closed: true,
+    _curved: false,
+    _automatic: true,
+    _beginning: 0,
+    _ending: 1.0,
+
+    _clip: false,
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var points = _.map(this.vertices, function(v) {
+        return v.clone();
+      });
+
+      var clone = new Path(points, this.closed, this.curved, !this.automatic);
+
+      _.each(Two.Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = {
+        vertices: _.map(this.vertices, function(v) {
+          return v.toObject();
+        })
+      };
+
+      _.each(Two.Shape.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      result.translation = this.translation.toObject;
+      result.rotation = this.rotation;
+      result.scale = this.scale;
+
+      return result;
+
+    },
+
+    noFill: function() {
+      this.fill = 'transparent';
+      return this;
+    },
+
+    noStroke: function() {
+      this.stroke = 'transparent';
+      return this;
+    },
+
+    /**
+     * Orient the vertices of the shape to the upper lefthand
+     * corner of the path.
+     */
+    corner: function() {
+
+      var rect = this.getBoundingClientRect(true);
+
+      rect.centroid = {
+        x: rect.left + rect.width / 2,
+        y: rect.top + rect.height / 2
+      };
+
+      _.each(this.vertices, function(v) {
+        v.addSelf(rect.centroid);
+      });
+
+      return this;
+
+    },
+
+    /**
+     * Orient the vertices of the shape to the center of the
+     * path.
+     */
+    center: function() {
+
+      var rect = this.getBoundingClientRect(true);
+
+      rect.centroid = {
+        x: rect.left + rect.width / 2,
+        y: rect.top + rect.height / 2
+      };
+
+      _.each(this.vertices, function(v) {
+        v.subSelf(rect.centroid);
+      });
+
+      // this.translation.addSelf(rect.centroid);
+
+      return this;
+
+    },
+
+    /**
+     * Remove self from the scene / parent.
+     */
+    remove: function() {
+
+      if (!this.parent) {
+        return this;
+      }
+
+      this.parent.remove(this);
+
+      return this;
+
+    },
+
+    /**
+     * Return an object with top, left, right, bottom, width, and height
+     * parameters of the group.
+     */
+    getBoundingClientRect: function(shallow) {
+      var matrix, border, l, x, y, i, v;
+
+      var left = Infinity, right = -Infinity,
+          top = Infinity, bottom = -Infinity;
+
+      // TODO: Update this to not __always__ update. Just when it needs to.
+      this._update(true);
+
+      matrix = !!shallow ? this._matrix : getComputedMatrix(this);
+
+      border = this.linewidth / 2;
+      l = this._vertices.length;
+
+      if (l <= 0) {
+        v = matrix.multiply(0, 0, 1);
+        return {
+          top: v.y,
+          left: v.x,
+          right: v.x,
+          bottom: v.y,
+          width: 0,
+          height: 0
+        };
+      }
+
+      for (i = 0; i < l; i++) {
+        v = this._vertices[i];
+
+        x = v.x;
+        y = v.y;
+
+        v = matrix.multiply(x, y, 1);
+        top = min(v.y - border, top);
+        left = min(v.x - border, left);
+        right = max(v.x + border, right);
+        bottom = max(v.y + border, bottom);
+      }
+
+      return {
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        width: right - left,
+        height: bottom - top
+      };
+
+    },
+
+    /**
+     * Given a float `t` from 0 to 1, return a point or assign a passed `obj`'s
+     * coordinates to that percentage on this Two.Path's curve.
+     */
+    getPointAt: function(t, obj) {
+      var ia, ib;
+      var x, x1, x2, x3, x4, y, y1, y2, y3, y4, left, right;
+      var target = this.length * Math.min(Math.max(t, 0), 1);
+      var length = this.vertices.length;
+      var last = length - 1;
+
+      var a = null;
+      var b = null;
+
+      for (var i = 0, l = this._lengths.length, sum = 0; i < l; i++) {
+
+        if (sum + this._lengths[i] >= target) {
+
+          if (this._closed) {
+            ia = Two.Utils.mod(i, length);
+            ib = Two.Utils.mod(i - 1, length);
+            if (i === 0) {
+              ia = ib;
+              ib = i;
+            }
+          } else {
+            ia = i;
+            ib = Math.min(Math.max(i - 1, 0), last);
+          }
+
+          a = this.vertices[ia];
+          b = this.vertices[ib];
+          target -= sum;
+          if (this._lengths[i] !== 0) {
+            t = target / this._lengths[i];
+          }
+
+          break;
+
+        }
+
+        sum += this._lengths[i];
+
+      }
+
+      // console.log(sum, a.command, b.command);
+
+      if (_.isNull(a) || _.isNull(b)) {
+        return null;
+      }
+
+      right = b.controls && b.controls.right;
+      left = a.controls && a.controls.left;
+
+      x1 = b.x;
+      y1 = b.y;
+      x2 = (right || b).x;
+      y2 = (right || b).y;
+      x3 = (left || a).x;
+      y3 = (left || a).y;
+      x4 = a.x;
+      y4 = a.y;
+
+      if (right && b._relative) {
+        x2 += b.x;
+        y2 += b.y;
+      }
+
+      if (left && a._relative) {
+        x3 += a.x;
+        y3 += a.y;
+      }
+
+      x = Two.Utils.getPointOnCubicBezier(t, x1, x2, x3, x4);
+      y = Two.Utils.getPointOnCubicBezier(t, y1, y2, y3, y4);
+
+      if (_.isObject(obj)) {
+        obj.x = x;
+        obj.y = y;
+        return obj;
+      }
+
+      return new Two.Vector(x, y);
+
+    },
+
+    /**
+     * Based on closed / curved and sorting of vertices plot where all points
+     * should be and where the respective handles should be too.
+     */
+    plot: function() {
+
+      if (this.curved) {
+        Two.Utils.getCurveFromPoints(this._vertices, this.closed);
+        return this;
+      }
+
+      for (var i = 0; i < this._vertices.length; i++) {
+        this._vertices[i]._command = i === 0 ? Two.Commands.move : Two.Commands.line;
+      }
+
+      return this;
+
+    },
+
+    subdivide: function(limit) {
+      //TODO: DRYness (function below)
+      this._update();
+
+      var last = this.vertices.length - 1;
+      var b = this.vertices[last];
+      var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
+      var points = [];
+      _.each(this.vertices, function(a, i) {
+
+        if (i <= 0 && !closed) {
+          b = a;
+          return;
+        }
+
+        if (a.command === Two.Commands.move) {
+          points.push(new Two.Anchor(b.x, b.y));
+          if (i > 0) {
+            points[points.length - 1].command = Two.Commands.line;
+          }
+          b = a;
+          return;
+        }
+
+        var verts = getSubdivisions(a, b, limit);
+        points = points.concat(verts);
+
+        // Assign commands to all the verts
+        _.each(verts, function(v, i) {
+          if (i <= 0 && b.command === Two.Commands.move) {
+            v.command = Two.Commands.move;
+          } else {
+            v.command = Two.Commands.line;
+          }
+        });
+
+        if (i >= last) {
+
+          // TODO: Add check if the two vectors in question are the same values.
+          if (this._closed && this._automatic) {
+
+            b = a;
+
+            verts = getSubdivisions(a, b, limit);
+            points = points.concat(verts);
+
+            // Assign commands to all the verts
+            _.each(verts, function(v, i) {
+              if (i <= 0 && b.command === Two.Commands.move) {
+                v.command = Two.Commands.move;
+              } else {
+                v.command = Two.Commands.line;
+              }
+            });
+
+          } else if (closed) {
+            points.push(new Two.Anchor(a.x, a.y));
+          }
+
+          points[points.length - 1].command = closed ? Two.Commands.close : Two.Commands.line;
+
+        }
+
+        b = a;
+
+      }, this);
+
+      this._automatic = false;
+      this._curved = false;
+      this.vertices = points;
+
+      return this;
+
+    },
+
+    _updateLength: function(limit) {
+      //TODO: DRYness (function above)
+      this._update();
+
+      var length = this.vertices.length;
+      var last = length - 1;
+      var b = this.vertices[last];
+      var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
+      var sum = 0;
+
+      if (_.isUndefined(this._lengths)) {
+        this._lengths = [];
+      }
+
+      _.each(this.vertices, function(a, i) {
+
+        if ((i <= 0 && !closed) || a.command === Two.Commands.move) {
+          b = a;
+          this._lengths[i] = 0;
+          return;
+        }
+
+        this._lengths[i] = getCurveLength(a, b, limit);
+        sum += this._lengths[i];
+
+        if (i >= last && closed) {
+
+          b = this.vertices[(i + 1) % length];
+
+          this._lengths[i + 1] = getCurveLength(a, b, limit);
+          sum += this._lengths[i + 1];
+
+        }
+
+        b = a;
+
+      }, this);
+
+      this._length = sum;
+
+      return this;
+
+    },
+
+    _update: function() {
+
+      if (this._flagVertices) {
+
+        var l = this.vertices.length;
+        var last = l - 1, v;
+
+        // TODO: Should clamp this so that `ia` and `ib`
+        // cannot select non-verts.
+        var ia = round((this._beginning) * last);
+        var ib = round((this._ending) * last);
+
+        this._vertices.length = 0;
+
+        for (var i = ia; i < ib + 1; i++) {
+          v = this.vertices[i];
+          this._vertices.push(v);
+        }
+
+        if (this._automatic) {
+          this.plot();
+        }
+
+      }
+
+      Two.Shape.prototype._update.apply(this, arguments);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagVertices =  this._flagFill =  this._flagStroke =
+         this._flagLinewidth = this._flagOpacity = this._flagVisible =
+         this._flagCap = this._flagJoin = this._flagMiter =
+         this._flagClip = false;
+
+      Two.Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Path.MakeObservable(Path.prototype);
+
+  /**
+   * Utility functions
+   */
+
+  function getCurveLength(a, b, limit) {
+    // TODO: DRYness
+    var x1, x2, x3, x4, y1, y2, y3, y4;
+
+    var right = b.controls && b.controls.right;
+    var left = a.controls && a.controls.left;
+
+    x1 = b.x;
+    y1 = b.y;
+    x2 = (right || b).x;
+    y2 = (right || b).y;
+    x3 = (left || a).x;
+    y3 = (left || a).y;
+    x4 = a.x;
+    y4 = a.y;
+
+    if (right && b._relative) {
+      x2 += b.x;
+      y2 += b.y;
+    }
+
+    if (left && a._relative) {
+      x3 += a.x;
+      y3 += a.y;
+    }
+
+    return Two.Utils.getCurveLength(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+  }
+
+  function getSubdivisions(a, b, limit) {
+    // TODO: DRYness
+    var x1, x2, x3, x4, y1, y2, y3, y4;
+
+    var right = b.controls && b.controls.right;
+    var left = a.controls && a.controls.left;
+
+    x1 = b.x;
+    y1 = b.y;
+    x2 = (right || b).x;
+    y2 = (right || b).y;
+    x3 = (left || a).x;
+    y3 = (left || a).y;
+    x4 = a.x;
+    y4 = a.y;
+
+    if (right && b._relative) {
+      x2 += b.x;
+      y2 += b.y;
+    }
+
+    if (left && a._relative) {
+      x3 += a.x;
+      y3 += a.y;
+    }
+
+    return Two.Utils.subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+  }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path;
+  var _ = Two.Utils;
+
+  var Line = Two.Line = function(x1, y1, x2, y2) {
+
+    var width = x2 - x1;
+    var height = y2 - y1;
+
+    var w2 = width / 2;
+    var h2 = height / 2;
+
+    Path.call(this, [
+        new Two.Anchor(- w2, - h2),
+        new Two.Anchor(w2, h2)
+    ]);
+
+    this.translation.set(x1 + w2, y1 + h2);
+
+  };
+
+  _.extend(Line.prototype, Path.prototype);
+
+  Path.MakeObservable(Line.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path;
+  var _ = Two.Utils;
+
+  var Rectangle = Two.Rectangle = function(x, y, width, height) {
+
+    Path.call(this, [
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor()
+    ], true);
+
+    this.width = width;
+    this.height = height;
+    this._update();
+
+    this.translation.set(x, y);
+
+  };
+
+  _.extend(Rectangle, {
+
+    Properties: ['width', 'height'],
+
+    MakeObservable: function(obj) {
+      Path.MakeObservable(obj);
+      _.each(Rectangle.Properties, Two.Utils.defineProperty, obj);
+    }
+
+  });
+
+  _.extend(Rectangle.prototype, Path.prototype, {
+
+    _width: 0,
+    _height: 0,
+
+    _flagWidth: 0,
+    _flagHeight: 0,
+
+    _update: function() {
+
+      if (this._flagWidth || this._flagHeight) {
+
+        var xr = this._width / 2;
+        var yr = this._height / 2;
+
+        this.vertices[0].set(-xr, -yr);
+        this.vertices[1].set(xr, -yr);
+        this.vertices[2].set(xr, yr);
+        this.vertices[3].set(-xr, yr);
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Rectangle.MakeObservable(Rectangle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+  var _ = Two.Utils;
+
+  var Ellipse = Two.Ellipse = function(ox, oy, rx, ry) {
+
+    if (!_.isNumber(ry)) {
+      ry = rx;
+    }
+
+    var amount = Two.Resolution;
+
+    var points = _.map(_.range(amount), function(i) {
+      return new Two.Anchor();
+    }, this);
+
+    Path.call(this, points, true, true);
+
+    this.width = rx * 2;
+    this.height = ry * 2;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  };
+
+  _.extend(Ellipse, {
+
+    Properties: ['width', 'height'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Ellipse.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(Ellipse.prototype, Path.prototype, {
+
+    _width: 0,
+    _height: 0,
+
+    _flagWidth: false,
+    _flagHeight: false,
+
+    _update: function() {
+
+      if (this._flagWidth || this._flagHeight) {
+        for (var i = 0, l = this.vertices.length; i < l; i++) {
+          var pct = i / l;
+          var theta = pct * TWO_PI;
+          var x = this._width * cos(theta) / 2;
+          var y = this._height * sin(theta) / 2;
+          this.vertices[i].set(x, y);
+        }
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = false;
+
+      Path.prototype.flagReset.call(this);
+      return this;
+
+    }
+
+  });
+
+  Ellipse.MakeObservable(Ellipse.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+  var _ = Two.Utils;
+
+  var Circle = Two.Circle = function(ox, oy, r) {
+
+    var amount = Two.Resolution;
+
+    var points = _.map(_.range(amount), function(i) {
+      return new Two.Anchor();
+    }, this);
+
+    Path.call(this, points, true, true);
+
+    this.radius = r;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  };
+
+  _.extend(Circle, {
+
+    Properties: ['radius'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Circle.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(Circle.prototype, Path.prototype, {
+
+    _radius: 0,
+    _flagRadius: false,
+
+    _update: function() {
+
+      if (this._flagRadius) {
+        for (var i = 0, l = this.vertices.length; i < l; i++) {
+          var pct = i / l;
+          var theta = pct * TWO_PI;
+          var x = this._radius * cos(theta);
+          var y = this._radius * sin(theta);
+          this.vertices[i].set(x, y);
+        }
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagRadius = false;
+
+      Path.prototype.flagReset.call(this);
+      return this;
+
+    }
+
+  });
+
+  Circle.MakeObservable(Circle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+  var _ = Two.Utils;
+
+  var Polygon = Two.Polygon = function(ox, oy, r, sides) {
+
+    sides = Math.max(sides || 0, 3);
+
+    var points = _.map(_.range(sides), function(i) {
+      return new Two.Anchor();
+    });
+
+    Path.call(this, points, true);
+
+    this.width = r * 2;
+    this.height = r * 2;
+    this.sides = sides;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  };
+
+  _.extend(Polygon, {
+
+    Properties: ['width', 'height', 'sides'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Polygon.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(Polygon.prototype, Path.prototype, {
+
+    _width: 0,
+    _height: 0,
+    _sides: 0,
+
+    _flagWidth: false,
+    _flagHeight: false,
+    _flagSides: false,
+
+    _update: function() {
+
+      if (this._flagWidth || this._flagHeight || this._flagSides) {
+
+        var sides = this._sides;
+        var amount = this.vertices.length;
+
+        if (amount > sides) {
+          this.vertices.splice(sides - 1, amount - sides);
+        }
+
+        for (var i = 0; i < sides; i++) {
+
+          var pct = (i + 0.5) / sides;
+          var theta = TWO_PI * pct + Math.PI / 2;
+          var x = this._width * cos(theta);
+          var y = this._height * sin(theta);
+
+          if (i >= amount) {
+            this.vertices.push(new Two.Anchor(x, y));
+          } else {
+            this.vertices[i].set(x, y);
+          }
+
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = this._flagSides = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Polygon.MakeObservable(Polygon.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path, PI = Math.PI, TWO_PI = Math.PI * 2, HALF_PI = Math.PI / 2,
+    cos = Math.cos, sin = Math.sin, abs = Math.abs, _ = Two.Utils;
+
+  var ArcSegment = Two.ArcSegment = function(ox, oy, ir, or, sa, ea, res) {
+
+    var points = _.map(_.range(res || (Two.Resolution * 3)), function() {
+      return new Two.Anchor();
+    });
+
+    Path.call(this, points, false, false, true);
+
+    this.innerRadius = ir;
+    this.outerRadius = or;
+
+    this.startAngle = sa;
+    this.endAngle = ea;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  }
+
+  _.extend(ArcSegment, {
+
+    Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(ArcSegment.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(ArcSegment.prototype, Path.prototype, {
+
+    _flagStartAngle: false,
+    _flagEndAngle: false,
+    _flagInnerRadius: false,
+    _flagOuterRadius: false,
+
+    _startAngle: 0,
+    _endAngle: TWO_PI,
+    _innerRadius: 0,
+    _outerRadius: 0,
+
+    _update: function() {
+
+      if (this._flagStartAngle || this._flagEndAngle || this._flagInnerRadius
+        || this._flagOuterRadius) {
+
+        var sa = this._startAngle;
+        var ea = this._endAngle;
+
+        var ir = this._innerRadius;
+        var or = this._outerRadius;
+
+        var connected = mod(sa, TWO_PI) === mod(ea, TWO_PI);
+        var punctured = ir > 0;
+
+        var vertices = this.vertices;
+        var length = (punctured ? vertices.length / 2 : vertices.length);
+        var command, id = 0;
+
+        if (connected) {
+          length--;
+        } else if (!punctured) {
+          length -= 2;
+        }
+
+        /**
+         * Outer Circle
+         */
+        for (var i = 0, last = length - 1; i < length; i++) {
+
+          var pct = i / last;
+          var v = vertices[id];
+          var theta = pct * (ea - sa) + sa;
+          var step = (ea - sa) / length;
+
+          var x = or * Math.cos(theta);
+          var y = or * Math.sin(theta);
+
+          switch (i) {
+            case 0:
+              command = Two.Commands.move;
+              break;
+            default:
+              command = Two.Commands.curve;
+          }
+
+          v.command = command;
+          v.x = x;
+          v.y = y;
+          v.controls.left.clear();
+          v.controls.right.clear();
+
+          if (v.command === Two.Commands.curve) {
+            var amp = or * step / Math.PI;
+            v.controls.left.x = amp * Math.cos(theta - HALF_PI);
+            v.controls.left.y = amp * Math.sin(theta - HALF_PI);
+            v.controls.right.x = amp * Math.cos(theta + HALF_PI);
+            v.controls.right.y = amp * Math.sin(theta + HALF_PI);
+            if (i === 1) {
+              v.controls.left.multiplyScalar(2);
+            }
+            if (i === last) {
+              v.controls.right.multiplyScalar(2);
+            }
+          }
+
+          id++;
+
+        }
+
+        if (punctured) {
+
+          if (connected) {
+            vertices[id].command = Two.Commands.close;
+            id++;
+          } else {
+            length--;
+            last = length - 1;
+          }
+
+          /**
+           * Inner Circle
+           */
+          for (i = 0; i < length; i++) {
+
+            pct = i / last;
+            v = vertices[id];
+            theta = (1 - pct) * (ea - sa) + sa;
+            step = (ea - sa) / length;
+
+            x = ir * Math.cos(theta);
+            y = ir * Math.sin(theta);
+            command = Two.Commands.curve;
+            if (i <= 0) {
+              command = connected ? Two.Commands.move : Two.Commands.line;
+            }
+
+            v.command = command;
+            v.x = x;
+            v.y = y;
+            v.controls.left.clear();
+            v.controls.right.clear();
+
+            if (v.command === Two.Commands.curve) {
+              amp = ir * step / Math.PI;
+              v.controls.left.x = amp * Math.cos(theta + HALF_PI);
+              v.controls.left.y = amp * Math.sin(theta + HALF_PI);
+              v.controls.right.x = amp * Math.cos(theta - HALF_PI);
+              v.controls.right.y = amp * Math.sin(theta - HALF_PI);
+              if (i === 1) {
+                v.controls.left.multiplyScalar(2);
+              }
+              if (i === last) {
+                v.controls.right.multiplyScalar(2);
+              }
+            }
+
+            id++;
+
+          }
+
+        } else if (!connected) {
+
+          vertices[id].command = Two.Commands.line;
+          vertices[id].x = 0;
+          vertices[id].y = 0;
+          id++;
+
+        }
+
+        /**
+         * Final Point
+         */
+        vertices[id].command = Two.Commands.close;
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      Path.prototype.flagReset.call(this);
+
+      this._flagStartAngle = this._flagEndAngle
+        = this._flagInnerRadius = this._flagOuterRadius = false;
+
+      return this;
+
+    }
+
+  });
+
+  ArcSegment.MakeObservable(ArcSegment.prototype);
+
+  function mod(v, l) {
+    while (v < 0) {
+      v += l;
+    }
+    return v % l;
+  }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+  var _ = Two.Utils;
+
+  var Star = Two.Star = function(ox, oy, or, ir, sides) {
+
+    if (!_.isNumber(ir)) {
+      ir = or / 2;
+    }
+
+    if (!_.isNumber(sides) || sides <= 0) {
+      sides = 5;
+    }
+
+    var length = sides * 2;
+
+    var points = _.map(_.range(length), function(i) {
+      return new Two.Anchor();
+    });
+
+    Path.call(this, points, true);
+
+    this.innerRadius = ir;
+    this.outerRadius = or;
+    this.sides = sides;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  };
+
+  _.extend(Star, {
+
+    Properties: ['innerRadius', 'outerRadius', 'sides'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Star.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(Star.prototype, Path.prototype, {
+
+    _innerRadius: 0,
+    _outerRadius: 0,
+    _sides: 0,
+
+    _flagInnerRadius: false,
+    _flagOuterRadius: false,
+    _flagSides: false,
+
+    _update: function() {
+
+      if (this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
+
+        var sides = this._sides * 2;
+        var amount = this.vertices.length;
+
+        if (amount > sides) {
+          this.vertices.splice(sides - 1, amount - sides);
+        }
+
+        for (var i = 0; i < sides; i++) {
+
+          var pct = (i + 0.5) / sides;
+          var theta = TWO_PI * pct;
+          var r = (i % 2 ? this._innerRadius : this._outerRadius);
+          var x = r * cos(theta);
+          var y = r * sin(theta);
+
+          if (i >= amount) {
+            this.vertices.push(new Two.Anchor(x, y));
+          } else {
+            this.vertices[i].set(x, y);
+          }
+
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Star.MakeObservable(Star.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var Path = Two.Path;
+  var _ = Two.Utils;
+
+  var RoundedRectangle = Two.RoundedRectangle = function(ox, oy, width, height, radius) {
+
+    if (!_.isNumber(radius)) {
+      radius = Math.floor(Math.min(width, height) / 12);
+    }
+
+    var amount = 10;
+
+    var points = _.map(_.range(amount), function(i) {
+      return new Two.Anchor(0, 0, 0, 0, 0, 0,
+        i === 0 ? Two.Commands.move : Two.Commands.curve);
+    });
+
+    points[points.length - 1].command = Two.Commands.close;
+
+    Path.call(this, points, false, false, true);
+
+    this.width = width;
+    this.height = height;
+    this.radius = radius;
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  };
+
+  _.extend(RoundedRectangle, {
+
+    Properties: ['width', 'height', 'radius'],
+
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(RoundedRectangle.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  });
+
+  _.extend(RoundedRectangle.prototype, Path.prototype, {
+
+    _width: 0,
+    _height: 0,
+    _radius: 0,
+
+    _flagWidth: false,
+    _flagHeight: false,
+    _flagRadius: false,
+
+    _update: function() {
+
+      if (this._flagWidth || this._flagHeight || this._flagRadius) {
+
+        var width = this._width;
+        var height = this._height;
+        var radius = Math.min(Math.max(this._radius, 0),
+          Math.min(width, height));
+
+        var v;
+        var w = width / 2;
+        var h = height / 2;
+
+        v = this.vertices[0];
+        v.x = - (w - radius);
+        v.y = - h;
+
+        // Upper Right Corner
+
+        v = this.vertices[1];
+        v.x = (w - radius);
+        v.y = - h;
+        v.controls.left.clear();
+        v.controls.right.x = radius;
+        v.controls.right.y = 0;
+
+        v = this.vertices[2];
+        v.x = w;
+        v.y = - (h - radius);
+        v.controls.right.clear();
+        v.controls.left.clear();
+
+        // Bottom Right Corner
+
+        v = this.vertices[3];
+        v.x = w;
+        v.y = (h - radius);
+        v.controls.left.clear();
+        v.controls.right.x = 0;
+        v.controls.right.y = radius;
+
+        v = this.vertices[4];
+        v.x = (w - radius);
+        v.y = h;
+        v.controls.right.clear();
+        v.controls.left.clear();
+
+        // Bottom Left Corner
+
+        v = this.vertices[5];
+        v.x = - (w - radius);
+        v.y = h;
+        v.controls.left.clear();
+        v.controls.right.x = - radius;
+        v.controls.right.y = 0;
+
+        v = this.vertices[6];
+        v.x = - w;
+        v.y = (h - radius);
+        v.controls.left.clear();
+        v.controls.right.clear();
+
+        // Upper Left Corner
+
+        v = this.vertices[7];
+        v.x = - w;
+        v.y = - (h - radius);
+        v.controls.left.clear();
+        v.controls.right.x = 0;
+        v.controls.right.y = - radius;
+
+        v = this.vertices[8];
+        v.x = - (w - radius);
+        v.y = - h;
+        v.controls.left.clear();
+        v.controls.right.clear();
+
+        v = this.vertices[9];
+        v.copy(this.vertices[8]);
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = this._flagRadius = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var root = Two.root;
+  var getComputedMatrix = Two.Utils.getComputedMatrix;
+  var _ = Two.Utils;
+
+  var canvas = (root.document ? root.document.createElement('canvas') : { getContext: _.identity });
+  var ctx = canvas.getContext('2d');
+
+  var Text = Two.Text = function(message, x, y, styles) {
+
+    Two.Shape.call(this);
+
+    this._renderer.type = 'text';
+    this._renderer.flagFill = _.bind(Text.FlagFill, this);
+    this._renderer.flagStroke = _.bind(Text.FlagStroke, this);
+
+    this.value = message;
+
+    if (_.isNumber(x)) {
+        this.translation.x = x;
+    }
+    if (_.isNumber(y)) {
+        this.translation.y = y;
+    }
+
+    if (!_.isObject(styles)) {
+      return this;
+    }
+
+    _.each(Two.Text.Properties, function(property) {
+
+      if (property in styles) {
+        this[property] = styles[property];
+      }
+
+    }, this);
+
+  };
+
+  _.extend(Two.Text, {
+
+    Properties: [
+      'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
+      'weight', 'decoration', 'baseline', 'opacity', 'visible', 'fill', 'stroke'
+    ],
+
+    FlagFill: function() {
+      this._flagFill = true;
+    },
+
+    FlagStroke: function() {
+      this._flagStroke = true;
+    },
+
+    MakeObservable: function(object) {
+
+      Two.Shape.MakeObservable(object);
+
+      _.each(Two.Text.Properties.slice(0, 12), Two.Utils.defineProperty, object);
+
+      Object.defineProperty(object, 'fill', {
+        enumerable: true,
+        get: function() {
+          return this._fill;
+        },
+        set: function(f) {
+
+          if (this._fill instanceof Two.Gradient
+            || this._fill instanceof Two.LinearGradient
+            || this._fill instanceof Two.RadialGradient
+            || this._fill instanceof Two.Texture) {
+            this._fill.unbind(Two.Events.change, this._renderer.flagFill);
+          }
+
+          this._fill = f;
+          this._flagFill = true;
+
+          if (this._fill instanceof Two.Gradient
+            || this._fill instanceof Two.LinearGradient
+            || this._fill instanceof Two.RadialGradient
+            || this._fill instanceof Two.Texture) {
+            this._fill.bind(Two.Events.change, this._renderer.flagFill);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'stroke', {
+        enumerable: true,
+        get: function() {
+          return this._stroke;
+        },
+        set: function(f) {
+
+          if (this._stroke instanceof Two.Gradient
+            || this._stroke instanceof Two.LinearGradient
+            || this._stroke instanceof Two.RadialGradient
+            || this._stroke instanceof Two.Texture) {
+            this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
+          }
+
+          this._stroke = f;
+          this._flagStroke = true;
+
+          if (this._stroke instanceof Two.Gradient
+            || this._stroke instanceof Two.LinearGradient
+            || this._stroke instanceof Two.RadialGradient
+            || this._stroke instanceof Two.Texture) {
+            this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'clip', {
+        enumerable: true,
+        get: function() {
+          return this._clip;
+        },
+        set: function(v) {
+          this._clip = v;
+          this._flagClip = true;
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Two.Text.prototype, Two.Shape.prototype, {
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    _flagValue: true,
+    _flagFamily: true,
+    _flagSize: true,
+    _flagLeading: true,
+    _flagAlignment: true,
+    _flagBaseline: true,
+    _flagStyle: true,
+    _flagWeight: true,
+    _flagDecoration: true,
+
+    _flagFill: true,
+    _flagStroke: true,
+    _flagLinewidth: true,
+    _flagOpacity: true,
+    _flagVisible: true,
+
+    _flagClip: false,
+
+    // Underlying Properties
+
+    _value: '',
+    _family: 'sans-serif',
+    _size: 13,
+    _leading: 17,
+    _alignment: 'center',
+    _baseline: 'middle',
+    _style: 'normal',
+    _weight: 500,
+    _decoration: 'none',
+
+    _fill: '#000',
+    _stroke: 'transparent',
+    _linewidth: 1,
+    _opacity: 1,
+    _visible: true,
+
+    _clip: false,
+
+    remove: function() {
+
+      if (!this.parent) {
+        return this;
+      }
+
+      this.parent.remove(this);
+
+      return this;
+
+    },
+
+    clone: function(parent) {
+
+      var parent = parent || this.parent;
+
+      var clone = new Two.Text(this.value);
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+
+      _.each(Two.Text.Properties, function(property) {
+        clone[property] = this[property];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = {
+        translation: this.translation.toObject(),
+        rotation: this.rotation,
+        scale: this.scale
+      };
+
+      _.each(Two.Text.Properties, function(property) {
+        result[property] = this[property];
+      }, this);
+
+      return result;
+
+    },
+
+    noStroke: function() {
+      this.stroke = 'transparent';
+      return this;
+    },
+
+    noFill: function() {
+      this.fill = 'transparent';
+      return this;
+    },
+
+    /**
+     * A shim to not break `getBoundingClientRect` calls. TODO: Implement a
+     * way to calculate proper bounding boxes of `Two.Text`.
+     */
+    getBoundingClientRect: function(shallow) {
+
+      var matrix, border, l, x, y, i, v;
+
+      var left = Infinity, right = -Infinity,
+          top = Infinity, bottom = -Infinity;
+
+      // TODO: Update this to not __always__ update. Just when it needs to.
+      this._update(true);
+
+      matrix = !!shallow ? this._matrix : getComputedMatrix(this);
+
+      v = matrix.multiply(0, 0, 1);
+
+      return {
+        top: v.x,
+        left: v.y,
+        right: v.x,
+        bottom: v.y,
+        width: 0,
+        height: 0
+      };
+
+    },
+
+    flagReset: function() {
+
+      this._flagValue = this._flagFamily = this._flagSize =
+        this._flagLeading = this._flagAlignment = this._flagFill =
+        this._flagStroke = this._flagLinewidth = this._flagOpaicty =
+        this._flagVisible = this._flagClip = this._flagDecoration =
+        this._flagBaseline = false;
+
+      Two.Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Two.Text.MakeObservable(Two.Text.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var Stop = Two.Stop = function(offset, color, opacity) {
+
+    this._renderer = {};
+    this._renderer.type = 'stop';
+
+    this.offset = _.isNumber(offset) ? offset
+      : Stop.Index <= 0 ? 0 : 1;
+
+    this.opacity = _.isNumber(opacity) ? opacity : 1;
+
+    this.color = _.isString(color) ? color
+      : Stop.Index <= 0 ? '#fff' : '#000';
+
+    Stop.Index = (Stop.Index + 1) % 2;
+
+  };
+
+  _.extend(Stop, {
+
+    Index: 0,
+
+    Properties: [
+      'offset',
+      'opacity',
+      'color'
+    ],
+
+    MakeObservable: function(object) {
+
+      _.each(Stop.Properties, function(property) {
+
+        var object = this;
+        var secret = '_' + property;
+        var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+        Object.defineProperty(object, property, {
+          enumerable: true,
+          get: function() {
+            return this[secret];
+          },
+          set: function(v) {
+            this[secret] = v;
+            this[flag] = true;
+            if (this.parent) {
+              this.parent._flagStops = true;
+            }
+          }
+        });
+
+      }, object);
+
+    }
+
+  });
+
+  _.extend(Stop.prototype, Two.Utils.Events, {
+
+    clone: function() {
+
+      var clone = new Stop();
+
+      _.each(Stop.Properties, function(property) {
+        clone[property] = this[property];
+      }, this);
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = {};
+
+      _.each(Stop.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      return result;
+
+    },
+
+    flagReset: function() {
+
+      this._flagOffset = this._flagColor = this._flagOpacity = false;
+
+      return this;
+
+    }
+
+  });
+
+  Stop.MakeObservable(Stop.prototype);
+
+  var Gradient = Two.Gradient = function(stops) {
+
+    this._renderer = {};
+    this._renderer.type = 'gradient';
+
+    this.id = Two.Identifier + Two.uniqueId();
+    this.classList = [];
+
+    this._renderer.flagStops = _.bind(Gradient.FlagStops, this);
+    this._renderer.bindStops = _.bind(Gradient.BindStops, this);
+    this._renderer.unbindStops = _.bind(Gradient.UnbindStops, this);
+
+    this.spread = 'pad';
+
+    this.stops = stops;
+
+  };
+
+  _.extend(Gradient, {
+
+    Stop: Stop,
+
+    Properties: [
+      'spread'
+    ],
+
+    MakeObservable: function(object) {
+
+      _.each(Gradient.Properties, Two.Utils.defineProperty, object);
+
+      Object.defineProperty(object, 'stops', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._stops;
+        },
+
+        set: function(stops) {
+
+          var updateStops = this._renderer.flagStops;
+          var bindStops = this._renderer.bindStops;
+          var unbindStops = this._renderer.unbindStops;
+
+          // Remove previous listeners
+          if (this._stops) {
+            this._stops
+              .unbind(Two.Events.insert, bindStops)
+              .unbind(Two.Events.remove, unbindStops);
+          }
+
+          // Create new Collection with copy of Stops
+          this._stops = new Two.Utils.Collection((stops || []).slice(0));
+
+          // Listen for Collection changes and bind / unbind
+          this._stops
+            .bind(Two.Events.insert, bindStops)
+            .bind(Two.Events.remove, unbindStops);
+
+          // Bind Initial Stops
+          bindStops(this._stops);
+
+        }
+
+      });
+
+    },
+
+    FlagStops: function() {
+      this._flagStops = true;
+    },
+
+    BindStops: function(items) {
+
+      // This function is called a lot
+      // when importing a large SVG
+      var i = items.length;
+      while(i--) {
+        items[i].bind(Two.Events.change, this._renderer.flagStops);
+        items[i].parent = this;
+      }
+
+      this._renderer.flagStops();
+
+    },
+
+    UnbindStops: function(items) {
+
+      var i = items.length;
+      while(i--) {
+        items[i].unbind(Two.Events.change, this._renderer.flagStops);
+        delete items[i].parent;
+      }
+
+      this._renderer.flagStops();
+
+    }
+
+  });
+
+  _.extend(Gradient.prototype, Two.Utils.Events, {
+
+    _flagStops: false,
+    _flagSpread: false,
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var stops = _.map(this.stops, function(s) {
+        return s.clone();
+      });
+
+      var clone = new Gradient(stops);
+
+      _.each(Two.Gradient.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = {
+        stops: _.map(this.stops, function(s) {
+          return s.toObject();
+        })
+      };
+
+      _.each(Gradient.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      return result;
+
+    },
+
+    _update: function() {
+
+      if (this._flagSpread || this._flagStops) {
+        this.trigger(Two.Events.change);
+      }
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagSpread = this._flagStops = false;
+
+      return this;
+
+    }
+
+  });
+
+  Gradient.MakeObservable(Gradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var LinearGradient = Two.LinearGradient = function(x1, y1, x2, y2, stops) {
+
+    Two.Gradient.call(this, stops);
+
+    this._renderer.type = 'linear-gradient';
+
+    var flagEndPoints = _.bind(LinearGradient.FlagEndPoints, this);
+    this.left = new Two.Vector().bind(Two.Events.change, flagEndPoints);
+    this.right = new Two.Vector().bind(Two.Events.change, flagEndPoints);
+
+    if (_.isNumber(x1)) {
+      this.left.x = x1;
+    }
+    if (_.isNumber(y1)) {
+      this.left.y = y1;
+    }
+    if (_.isNumber(x2)) {
+      this.right.x = x2;
+    }
+    if (_.isNumber(y2)) {
+      this.right.y = y2;
+    }
+
+  };
+
+  _.extend(LinearGradient, {
+
+    Stop: Two.Gradient.Stop,
+
+    MakeObservable: function(object) {
+      Two.Gradient.MakeObservable(object);
+    },
+
+    FlagEndPoints: function() {
+      this._flagEndPoints = true;
+    }
+
+  });
+
+  _.extend(LinearGradient.prototype, Two.Gradient.prototype, {
+
+    _flagEndPoints: false,
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var stops = _.map(this.stops, function(stop) {
+        return stop.clone();
+      });
+
+      var clone = new LinearGradient(this.left._x, this.left._y,
+        this.right._x, this.right._y, stops);
+
+      _.each(Two.Gradient.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = Two.Gradient.prototype.toObject.call(this);
+
+      result.left = this.left.toObject();
+      result.right = this.right.toObject();
+
+      return result;
+
+    },
+
+    _update: function() {
+
+      if (this._flagEndPoints || this._flagSpread || this._flagStops) {
+        this.trigger(Two.Events.change);
+      }
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagEndPoints = false;
+
+      Two.Gradient.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  LinearGradient.MakeObservable(LinearGradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+
+  var RadialGradient = Two.RadialGradient = function(cx, cy, r, stops, fx, fy) {
+
+    Two.Gradient.call(this, stops);
+
+    this._renderer.type = 'radial-gradient';
+
+    this.center = new Two.Vector()
+      .bind(Two.Events.change, _.bind(function() {
+        this._flagCenter = true;
+      }, this));
+
+    this.radius = _.isNumber(r) ? r : 20;
+
+    this.focal = new Two.Vector()
+      .bind(Two.Events.change, _.bind(function() {
+        this._flagFocal = true;
+      }, this));
+
+    if (_.isNumber(cx)) {
+      this.center.x = cx;
+    }
+    if (_.isNumber(cy)) {
+      this.center.y = cy;
+    }
+
+    this.focal.copy(this.center);
+
+    if (_.isNumber(fx)) {
+      this.focal.x = fx;
+    }
+    if (_.isNumber(fy)) {
+      this.focal.y = fy;
+    }
+
+  };
+
+  _.extend(RadialGradient, {
+
+    Stop: Two.Gradient.Stop,
+
+    Properties: [
+      'radius'
+    ],
+
+    MakeObservable: function(object) {
+
+      Two.Gradient.MakeObservable(object);
+
+      _.each(RadialGradient.Properties, Two.Utils.defineProperty, object);
+
+    }
+
+  });
+
+  _.extend(RadialGradient.prototype, Two.Gradient.prototype, {
+
+    _flagRadius: false,
+    _flagCenter: false,
+    _flagFocal: false,
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var stops = _.map(this.stops, function(stop) {
+        return stop.clone();
+      });
+
+      var clone = new RadialGradient(this.center._x, this.center._y,
+          this._radius, stops, this.focal._x, this.focal._y);
+
+      _.each(Two.Gradient.Properties.concat(RadialGradient.Properties), function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    toObject: function() {
+
+      var result = Two.Gradient.prototype.toObject.call(this);
+
+      _.each(RadialGradient.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      result.center = this.center.toObject();
+      result.focal = this.focal.toObject();
+
+      return result;
+
+    },
+
+    _update: function() {
+
+      if (this._flagRadius || this._flatCenter || this._flagFocal
+        || this._flagSpread || this._flagStops) {
+        this.trigger(Two.Events.change);
+      }
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagRadius = this._flagCenter = this._flagFocal = false;
+
+      Two.Gradient.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  RadialGradient.MakeObservable(RadialGradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+  var anchor;
+  var regex = {
+    video: /\.(mp4|webm)$/i,
+    image: /\.(jpe?g|png|gif|tiff)$/i
+  };
+
+  if (this.document) {
+    anchor = document.createElement('a');
+  }
+
+  var Texture = Two.Texture = function(src, callback) {
+
+    this._renderer = {};
+    this._renderer.type = 'texture';
+    this._renderer.flagOffset = _.bind(Texture.FlagOffset, this);
+    this._renderer.flagScale = _.bind(Texture.FlagScale, this);
+
+    this.id = Two.Identifier + Two.uniqueId();
+    this.classList = [];
+
+    this.offset = new Two.Vector();
+
+    if (_.isFunction(callback)) {
+      var loaded = _.bind(function() {
+        this.unbind(Two.Events.load, loaded);
+        if (_.isFunction(callback)) {
+          callback();
+        }
+      }, this);
+      this.bind(Two.Events.load, loaded);
+    }
+
+    if (_.isString(src)) {
+      this.src = src;
+    } else if (_.isElement(src)) {
+      this.image = src;
+    }
+
+    this._update();
+
+  };
+
+  _.extend(Texture, {
+
+    Properties: [
+      'src',
+      'loaded',
+      'repeat'
+    ],
+
+    ImageRegistry: new Two.Registry(),
+
+    getAbsoluteURL: function(path) {
+      if (!anchor) {
+        // TODO: Fix for headless environment
+        return path;
+      }
+      anchor.href = path;
+      return anchor.href;
+    },
+
+    getImage: function(src) {
+
+      var absoluteSrc = Texture.getAbsoluteURL(src);
+
+      if (Texture.ImageRegistry.contains(absoluteSrc)) {
+        return Texture.ImageRegistry.get(absoluteSrc);
+      }
+
+      var image;
+
+      if (regex.video.test(absoluteSrc)) {
+        image = document.createElement('video');
+      } else {
+        image = document.createElement('img');
+      }
+
+      image.crossOrigin = 'anonymous';
+
+      return image;
+
+    },
+
+    Register: {
+      canvas: function(texture, callback) {
+        texture._src = '#' + texture.id;
+        Texture.ImageRegistry.add(texture.src, texture.image);
+        if (_.isFunction(callback)) {
+          callback();
+        }
+      },
+      img: function(texture, callback) {
+
+        var loaded = function(e) {
+          texture.image.removeEventListener('load', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          if (_.isFunction(callback)) {
+            callback();
+          }
+        };
+        var error = function(e) {
+          texture.image.removeEventListener('load', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          throw new Two.Utils.Error('unable to load ' + texture.src);
+        };
+
+        if (_.isNumber(texture.image.width) && texture.image.width > 0
+          && _.isNumber(texture.image.height) && texture.image.height > 0) {
+            loaded();
+        } else {
+          texture.image.addEventListener('load', loaded, false);
+          texture.image.addEventListener('error', error, false);
+        }
+
+        texture._src = Texture.getAbsoluteURL(texture._src);
+
+        if (texture.image && texture.image.getAttribute('two-src')) {
+          return;
+        }
+
+        texture.image.setAttribute('two-src', texture.src);
+        Texture.ImageRegistry.add(texture.src, texture.image);
+        texture.image.src = texture.src;
+
+      },
+      video: function(texture, callback) {
+
+        var loaded = function(e) {
+          texture.image.removeEventListener('load', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          texture.image.width = texture.image.videoWidth;
+          texture.image.height = texture.image.videoHeight;
+          texture.image.play();
+          if (_.isFunction(callback)) {
+            callback();
+          }
+        };
+        var error = function(e) {
+          texture.image.removeEventListener('load', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          throw new Two.Utils.Error('unable to load ' + texture.src);
+        };
+
+        texture._src = Texture.getAbsoluteURL(texture._src);
+        texture.image.addEventListener('canplaythrough', loaded, false);
+        texture.image.addEventListener('error', error, false);
+
+        if (texture.image && texture.image.getAttribute('two-src')) {
+          return;
+        }
+
+        texture.image.setAttribute('two-src', texture.src);
+        Texture.ImageRegistry.add(texture.src, texture.image);
+        texture.image.src = texture.src;
+        texture.image.loop = true;
+        texture.image.load();
+
+      }
+    },
+
+    load: function(texture, callback) {
+
+      var src = texture.src;
+      var image = texture.image;
+      var tag = image && image.nodeName.toLowerCase();
+
+      if (texture._flagImage) {
+        if (/canvas/i.test(tag)) {
+          Texture.Register.canvas(texture, callback);
+        } else {
+          texture._src = image.getAttribute('two-src') || image.src;
+          Texture.Register[tag](texture, callback);
+        }
+      }
+
+      if (texture._flagSrc) {
+        if (!image) {
+          texture.image = Texture.getImage(texture.src);
+        }
+        tag = texture.image.nodeName.toLowerCase();
+        Texture.Register[tag](texture, callback);
+      }
+
+    },
+
+    FlagOffset: function() {
+      this._flagOffset = true;
+    },
+
+    FlagScale: function() {
+      this._flagScale = true;
+    },
+
+    MakeObservable: function(object) {
+
+      _.each(Texture.Properties, Two.Utils.defineProperty, object);
+
+      Object.defineProperty(object, 'image', {
+        enumerable: true,
+        get: function() {
+          return this._image;
+        },
+        set: function(image) {
+
+          var tag = image && image.nodeName.toLowerCase();
+          var index;
+
+          switch (tag) {
+            case 'canvas':
+              index = '#' + image.id;
+              break;
+            default:
+              index = image.src;
+          }
+
+          if (Texture.ImageRegistry.contains(index)) {
+            this._image = Texture.ImageRegistry.get(image.src);
+          } else {
+            this._image = image;
+          }
+
+          this._flagImage = true;
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'offset', {
+        enumerable: true,
+        get: function() {
+          return this._offset;
+        },
+        set: function(v) {
+          if (this._offset) {
+            this._offset.unbind(Two.Events.change, this._renderer.flagOffset);
+          }
+          this._offset = v;
+          this._offset.bind(Two.Events.change, this._renderer.flagOffset);
+          this._flagOffset = true;
+        }
+      });
+
+      Object.defineProperty(object, 'scale', {
+        enumerable: true,
+        get: function() {
+          return this._scale;
+        },
+        set: function(v) {
+
+          if (this._scale instanceof Two.Vector) {
+            this._scale.unbind(Two.Events.change, this._renderer.flagScale);
+          }
+
+          this._scale = v;
+
+          if (this._scale instanceof Two.Vector) {
+            this._scale.bind(Two.Events.change, this._renderer.flagScale);
+          }
+
+          this._flagScale = true;
+
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Texture.prototype, Two.Utils.Events, Two.Shape.prototype, {
+
+    _flagSrc: false,
+    _flagImage: false,
+    _flagVideo: false,
+    _flagLoaded: false,
+    _flagRepeat: false,
+
+    _flagOffset: false,
+    _flagScale: false,
+
+    _src: '',
+    _image: null,
+    _loaded: false,
+    _repeat: 'no-repeat',
+
+    _scale: 1,
+    _offset: null,
+
+    clone: function() {
+      return new Texture(this.src);
+    },
+
+    toObject: function() {
+      return {
+        src: this.src,
+        image: this.image
+      }
+    },
+
+    _update: function() {
+
+      if (this._flagSrc || this._flagImage || this._flagVideo) {
+
+        this.trigger(Two.Events.change);
+
+        if (this._flagSrc || this._flagImage) {
+          this.loaded = false;
+          Texture.load(this, _.bind(function() {
+            this.loaded = true;
+            this
+              .trigger(Two.Events.change)
+              .trigger(Two.Events.load);
+          }, this));
+        }
+
+      }
+
+      if (this._image && this._image.readyState >= 4) {
+        this._flagVideo = true;
+      }
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagSrc = this._flagImage = this._flagLoaded
+        = this._flagVideo = this._flagScale = this._flagOffset = false;
+
+      return this;
+
+    }
+
+  });
+
+  Texture.MakeObservable(Texture.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+  var Path = Two.Path;
+  var Rectangle = Two.Rectangle;
+
+  var Sprite = Two.Sprite = function(path, ox, oy, cols, rows, frameRate) {
+
+    Path.call(this, [
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor()
+    ], true);
+
+    this.noStroke();
+    this.noFill();
+
+    if (path instanceof Two.Texture) {
+      this.texture = path;
+    } else if (_.isString(path)) {
+      this.texture = new Two.Texture(path);
+    }
+
+    this._update();
+    this.translation.set(ox || 0, oy || 0);
+
+    if (_.isNumber(cols)) {
+      this.columns = cols;
+    }
+    if (_.isNumber(rows)) {
+      this.rows = rows;
+    }
+    if (_.isNumber(frameRate)) {
+      this.frameRate = frameRate;
+    }
+
+  };
+
+  _.extend(Sprite, {
+
+    Properties: [
+      'texture', 'columns', 'rows', 'frameRate', 'index'
+    ],
+
+    MakeObservable: function(obj) {
+
+      Rectangle.MakeObservable(obj);
+      _.each(Sprite.Properties, Two.Utils.defineProperty, obj);
+
+    }
+
+  })
+
+  _.extend(Sprite.prototype, Rectangle.prototype, {
+
+    _flagTexture: false,
+    _flagColumns: false,
+    _flagRows: false,
+    _flagFrameRate: false,
+    flagIndex: false,
+
+    // Private variables
+    _amount: 1,
+    _duration: 0,
+    _startTime: 0,
+    _playing: false,
+    _firstFrame: 0,
+    _lastFrame: 0,
+    _loop: true,
+
+    // Exposed through getter-setter
+    _texture: null,
+    _columns: 1,
+    _rows: 1,
+    _frameRate: 0,
+    _index: 0,
+
+    play: function(firstFrame, lastFrame, onLastFrame) {
+
+      this._playing = true;
+      this._firstFrame = 0;
+      this._lastFrame = this.amount - 1;
+      this._startTime = _.performance.now();
+
+      if (_.isNumber(firstFrame)) {
+        this._firstFrame = firstFrame;
+      }
+      if (_.isNumber(lastFrame)) {
+        this._lastFrame = lastFrame;
+      }
+      if (_.isFunction(onLastFrame)) {
+        this._onLastFrame = onLastFrame;
+      } else {
+        delete this._onLastFrame;
+      }
+
+      if (this._index !== this._firstFrame) {
+        this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
+          / this._frameRate;
+      }
+
+      return this;
+
+    },
+
+    pause: function() {
+
+      this._playing = false;
+      return this;
+
+    },
+
+    stop: function() {
+
+      this._playing = false;
+      this._index = 0;
+
+      return this;
+
+    },
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var clone = new Sprite(
+        this.texture, this.translation.x, this.translation.y,
+        this.columns, this.rows, this.frameRate
+      );
+
+      if (this.playing) {
+        clone.play(this._firstFrame, this._lastFrame);
+        clone._loop = this._loop;
+      }
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    _update: function() {
+
+      var effect = this._texture;
+      var cols = this._columns;
+      var rows = this._rows;
+
+      var width, height, elapsed, amount, duration;
+      var index, iw, ih, isRange, frames;
+
+      if (this._flagColumns || this._flagRows) {
+        this._amount = this._columns * this._rows;
+      }
+
+      if (this._flagFrameRate) {
+        this._duration = 1000 * this._amount / this._frameRate;
+      }
+
+      if (this._flagTexture) {
+        this.fill = this._texture;
+      }
+
+      if (this._texture.loaded) {
+
+        iw = effect.image.width;
+        ih = effect.image.height;
+
+        width = iw / cols;
+        height = ih / rows;
+        amount = this._amount;
+
+        if (this.width !== width) {
+          this.width = width;
+        }
+        if (this.height !== height) {
+          this.height = height;
+        }
+
+        if (this._playing && this._frameRate > 0) {
+
+          if (_.isNaN(this._lastFrame)) {
+            this._lastFrame = amount - 1;
+          }
+
+          // TODO: Offload perf logic to instance of `Two`.
+          elapsed = _.performance.now() - this._startTime;
+          frames = this._lastFrame + 1;
+          duration = 1000 * (frames - this._firstFrame) / this._frameRate;
+
+          if (this._loop) {
+            elapsed = elapsed % duration;
+          } else {
+            elapsed = Math.min(elapsed, duration);
+          }
+
+          index = _.lerp(this._firstFrame, frames, elapsed / duration);
+          index = Math.floor(index);
+
+          if (index !== this._index) {
+            this._index = index;
+            if (index >= this._lastFrame - 1 && this._onLastFrame) {
+              this._onLastFrame();  // Shortcut for chainable sprite animations
+            }
+          }
+
+        }
+
+        var col = this._index % cols;
+        var row = Math.floor(this._index / cols);
+
+        var ox = - width * col + (iw - width) / 2;
+        var oy = - height * row + (ih - height) / 2;
+
+        // TODO: Improve performance
+        if (ox !== effect.offset.x) {
+          effect.offset.x = ox;
+        }
+        if (oy !== effect.offset.y) {
+          effect.offset.y = oy;
+        }
+
+      }
+
+      Rectangle.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagTexture = this._flagColumns = this._flagRows
+        = this._flagFrameRate = false;
+
+      Rectangle.prototype.flagReset.call(this);
+
+      return this;
+    }
+
+
+  });
+
+  Sprite.MakeObservable(Sprite.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  var _ = Two.Utils;
+  var Path = Two.Path;
+  var Rectangle = Two.Rectangle;
+
+  var ImageSequence = Two.ImageSequence = function(paths, ox, oy, frameRate) {
+
+    Path.call(this, [
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor(),
+      new Two.Anchor()
+    ], true);
+
+    this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this);
+    this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this);
+    this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this);
+
+    this.noStroke();
+    this.noFill();
+
+    this.textures = _.map(paths, ImageSequence.GenerateTexture, this);
+
+    this._update();
+    this.translation.set(ox || 0, oy || 0);
+
+    if (_.isNumber(frameRate)) {
+      this.frameRate = frameRate;
+    } else {
+      this.frameRate = ImageSequence.DefaultFrameRate;
+    }
+
+  };
+
+  _.extend(ImageSequence, {
+
+    Properties: [
+      'frameRate',
+      'index'
+    ],
+
+    DefaultFrameRate: 30,
+
+    FlagTextures: function() {
+      this._flagTextures = true;
+    },
+
+    BindTextures: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].bind(Two.Events.change, this._renderer.flagTextures);
+      }
+
+      this._renderer.flagTextures();
+
+    },
+
+    UnbindTextures: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].unbind(Two.Events.change, this._renderer.flagTextures);
+      }
+
+      this._renderer.flagTextures();
+
+    },
+
+    MakeObservable: function(obj) {
+
+      Rectangle.MakeObservable(obj);
+      _.each(ImageSequence.Properties, Two.Utils.defineProperty, obj);
+
+      Object.defineProperty(obj, 'textures', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._textures;
+        },
+
+        set: function(textures) {
+
+          var updateTextures = this._renderer.flagTextures;
+          var bindTextures = this._renderer.bindTextures;
+          var unbindTextures = this._renderer.unbindTextures;
+
+          // Remove previous listeners
+          if (this._textures) {
+            this._textures
+              .unbind(Two.Events.insert, bindTextures)
+              .unbind(Two.Events.remove, unbindTextures);
+          }
+
+          // Create new Collection with copy of vertices
+          this._textures = new Two.Utils.Collection((textures || []).slice(0));
+
+          // Listen for Collection changes and bind / unbind
+          this._textures
+            .bind(Two.Events.insert, bindTextures)
+            .bind(Two.Events.remove, unbindTextures);
+
+          // Bind Initial Textures
+          bindTextures(this._textures);
+
+        }
+
+      });
+
+    },
+
+    GenerateTexture: function(obj) {
+      if (obj instanceof Two.Texture) {
+        return obj;
+      } else if (_.isString(obj)) {
+        return new Two.Texture(obj);
+      }
+    }
+
+  });
+
+  _.extend(ImageSequence.prototype, Rectangle.prototype, {
+
+    _flagTextures: false,
+    _flagFrameRate: false,
+    _flagIndex: false,
+
+    // Private variables
+    _amount: 1,
+    _duration: 0,
+    _index: 0,
+    _startTime: 0,
+    _playing: false,
+    _firstFrame: 0,
+    _lastFrame: 0,
+    _loop: true,
+
+    // Exposed through getter-setter
+    _textures: null,
+    _frameRate: 0,
+
+    play: function(firstFrame, lastFrame, onLastFrame) {
+
+      this._playing = true;
+      this._firstFrame = 0;
+      this._lastFrame = this.amount - 1;
+      this._startTime = _.performance.now();
+
+      if (_.isNumber(firstFrame)) {
+        this._firstFrame = firstFrame;
+      }
+      if (_.isNumber(lastFrame)) {
+        this._lastFrame = lastFrame;
+      }
+      if (_.isFunction(onLastFrame)) {
+        this._onLastFrame = onLastFrame;
+      } else {
+        delete this._onLastFrame;
+      }
+
+      if (this._index !== this._firstFrame) {
+        this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
+          / this._frameRate;
+      }
+
+      return this;
+
+    },
+
+    pause: function() {
+
+      this._playing = false;
+      return this;
+
+    },
+
+    stop: function() {
+
+      this._playing = false;
+      this._index = 0;
+
+      return this;
+
+    },
+
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var clone = new ImageSequence(this.textures, this.translation.x,
+        this.translation.y, this.frameRate)
+
+        clone._loop = this._loop;
+
+        if (this._playing) {
+          clone.play();
+        }
+
+        if (parent) {
+          parent.add(clone);
+        }
+
+        return clone;
+
+    },
+
+    _update: function() {
+
+      var effects = this._textures;
+      var width, height, elapsed, amount, duration, texture;
+      var index, frames;
+
+      if (this._flagTextures) {
+        this._amount = effects.length;
+      }
+
+      if (this._flagFrameRate) {
+        this._duration = 1000 * this._amount / this._frameRate;
+      }
+
+      if (this._playing && this._frameRate > 0) {
+
+        amount = this._amount;
+
+        if (_.isNaN(this._lastFrame)) {
+          this._lastFrame = amount - 1;
+        }
+
+        // TODO: Offload perf logic to instance of `Two`.
+        elapsed = _.performance.now() - this._startTime;
+        frames = this._lastFrame + 1;
+        duration = 1000 * (frames - this._firstFrame) / this._frameRate;
+
+        if (this._loop) {
+          elapsed = elapsed % duration;
+        } else {
+          elapsed = Math.min(elapsed, duration);
+        }
+
+        index = _.lerp(this._firstFrame, frames, elapsed / duration);
+        index = Math.floor(index);
+
+        if (index !== this._index) {
+
+          this._index = index;
+          texture = effects[this._index];
+
+          if (texture.loaded) {
+
+            width = texture.image.width;
+            height = texture.image.height;
+
+            if (this.width !== width) {
+              this.width = width;
+            }
+            if (this.height !== height) {
+              this.height = height;
+            }
+
+            this.fill = texture;
+
+            if (index >= this._lastFrame - 1 && this._onLastFrame) {
+              this._onLastFrame();  // Shortcut for chainable sprite animations
+            }
+
+          }
+
+        }
+
+      } else if (this._flagIndex || !(this.fill instanceof Two.Texture)) {
+
+        texture = effects[this._index];
+
+        if (texture.loaded) {
+
+          width = texture.image.width;
+          height = texture.image.height;
+
+          if (this.width !== width) {
+            this.width = width;
+          }
+          if (this.height !== height) {
+            this.height = height;
+          }
+
+        }
+
+        this.fill = texture;
+
+      }
+
+      Rectangle.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    flagReset: function() {
+
+      this._flagTextures = this._flagFrameRate = false;
+      Rectangle.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  ImageSequence.MakeObservable(ImageSequence.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+  /**
+   * Constants
+   */
+  var min = Math.min, max = Math.max;
+  var _ = Two.Utils;
+
+  /**
+   * A children collection which is accesible both by index and by object id
+   * @constructor
+   */
+  var Children = function() {
+
+    Two.Utils.Collection.apply(this, arguments);
+
+    Object.defineProperty(this, '_events', {
+      value : {},
+      enumerable: false
+    });
+
+    this.ids = {};
+
+    this.on(Two.Events.insert, this.attach);
+    this.on(Two.Events.remove, this.detach);
+    Children.prototype.attach.apply(this, arguments);
+
+  };
+
+  Children.prototype = new Two.Utils.Collection();
+  Children.prototype.constructor = Children;
+
+  _.extend(Children.prototype, {
+
+    attach: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        this.ids[children[i].id] = children[i];
+      }
+      return this;
+    },
+
+    detach: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        delete this.ids[children[i].id];
+      }
+      return this;
+    }
+
+  });
+
+  var Group = Two.Group = function() {
+
+    Two.Shape.call(this, true);
+
+    this._renderer.type = 'group';
+
+    this.additions = [];
+    this.subtractions = [];
+
+    this.children = arguments;
+
+  };
+
+  _.extend(Group, {
+
+    Children: Children,
+
+    InsertChildren: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        replaceParent.call(this, children[i], this);
+      }
+    },
+
+    RemoveChildren: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        replaceParent.call(this, children[i]);
+      }
+    },
+
+    OrderChildren: function(children) {
+      this._flagOrder = true;
+    },
+
+    MakeObservable: function(object) {
+
+      var properties = Two.Path.Properties.slice(0);
+      var oi = _.indexOf(properties, 'opacity');
+
+      if (oi >= 0) {
+
+        properties.splice(oi, 1);
+
+        Object.defineProperty(object, 'opacity', {
+
+          enumerable: true,
+
+          get: function() {
+            return this._opacity;
+          },
+
+          set: function(v) {
+            // Only set flag if there is an actual difference
+            this._flagOpacity = (this._opacity != v);
+            this._opacity = v;
+          }
+
+        });
+
+      }
+
+      Two.Shape.MakeObservable(object);
+      Group.MakeGetterSetters(object, properties);
+
+      Object.defineProperty(object, 'children', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._children;
+        },
+
+        set: function(children) {
+
+          var insertChildren = _.bind(Group.InsertChildren, this);
+          var removeChildren = _.bind(Group.RemoveChildren, this);
+          var orderChildren = _.bind(Group.OrderChildren, this);
+
+          if (this._children) {
+            this._children.unbind();
+          }
+
+          this._children = new Children(children);
+          this._children.bind(Two.Events.insert, insertChildren);
+          this._children.bind(Two.Events.remove, removeChildren);
+          this._children.bind(Two.Events.order, orderChildren);
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'mask', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._mask;
+        },
+
+        set: function(v) {
+          this._mask = v;
+          this._flagMask = true;
+          if (!v.clip) {
+            v.clip = true;
+          }
+        }
+
+      });
+
+    },
+
+    MakeGetterSetters: function(group, properties) {
+
+      if (!_.isArray(properties)) {
+        properties = [properties];
+      }
+
+      _.each(properties, function(k) {
+        Group.MakeGetterSetter(group, k);
+      });
+
+    },
+
+    MakeGetterSetter: function(group, k) {
+
+      var secret = '_' + k;
+
+      Object.defineProperty(group, k, {
+
+        enumerable: true,
+
+        get: function() {
+          return this[secret];
+        },
+
+        set: function(v) {
+          this[secret] = v;
+          _.each(this.children, function(child) { // Trickle down styles
+            child[k] = v;
+          });
+        }
+
+      });
+
+    }
+
+  });
+
+  _.extend(Group.prototype, Two.Shape.prototype, {
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    _flagAdditions: false,
+    _flagSubtractions: false,
+    _flagOrder: false,
+    _flagOpacity: true,
+
+    _flagMask: false,
+
+    // Underlying Properties
+
+    _fill: '#fff',
+    _stroke: '#000',
+    _linewidth: 1.0,
+    _opacity: 1.0,
+    _visible: true,
+
+    _cap: 'round',
+    _join: 'round',
+    _miter: 4,
+
+    _closed: true,
+    _curved: false,
+    _automatic: true,
+    _beginning: 0,
+    _ending: 1.0,
+
+    _mask: null,
+
+    /**
+     * TODO: Group has a gotcha in that it's at the moment required to be bound to
+     * an instance of two in order to add elements correctly. This needs to
+     * be rethought and fixed.
+     */
+    clone: function(parent) {
+
+      parent = parent || this.parent;
+
+      var group = new Group();
+      var children = _.map(this.children, function(child) {
+        return child.clone(group);
+      });
+
+      group.add(children);
+
+      group.opacity = this.opacity;
+
+      if (this.mask) {
+        group.mask = this.mask;
+      }
+
+      group.translation.copy(this.translation);
+      group.rotation = this.rotation;
+      group.scale = this.scale;
+
+      if (parent) {
+        parent.add(group);
+      }
+
+      return group;
+
+    },
+
+    /**
+     * Export the data from the instance of Two.Group into a plain JavaScript
+     * object. This also makes all children plain JavaScript objects. Great
+     * for turning into JSON and storing in a database.
+     */
+    toObject: function() {
+
+      var result = {
+        children: [],
+        translation: this.translation.toObject(),
+        rotation: this.rotation,
+        scale: this.scale,
+        opacity: this.opacity,
+        mask: (this.mask ? this.mask.toObject() : null)
+      };
+
+      _.each(this.children, function(child, i) {
+        result.children[i] = child.toObject();
+      }, this);
+
+      return result;
+
+    },
+
+    /**
+     * Anchor all children to the upper left hand corner
+     * of the group.
+     */
+    corner: function() {
+
+      var rect = this.getBoundingClientRect(true),
+       corner = { x: rect.left, y: rect.top };
+
+      this.children.forEach(function(child) {
+        child.translation.subSelf(corner);
+      });
+
+      return this;
+
+    },
+
+    /**
+     * Anchors all children around the center of the group,
+     * effectively placing the shape around the unit circle.
+     */
+    center: function() {
+
+      var rect = this.getBoundingClientRect(true);
+
+      rect.centroid = {
+        x: rect.left + rect.width / 2,
+        y: rect.top + rect.height / 2
+      };
+
+      this.children.forEach(function(child) {
+        if (child.isShape) {
+          child.translation.subSelf(rect.centroid);
+        }
+      });
+
+      // this.translation.copy(rect.centroid);
+
+      return this;
+
+    },
+
+    /**
+     * Recursively search for id. Returns the first element found.
+     * Returns null if none found.
+     */
+    getById: function (id) {
+      var search = function (node, id) {
+        if (node.id === id) {
+          return node;
+        } else if (node.children) {
+          var i = node.children.length;
+          while (i--) {
+            var found = search(node.children[i], id);
+            if (found) return found;
+          }
+        }
+
+      };
+      return search(this, id) || null;
+    },
+
+    /**
+     * Recursively search for classes. Returns an array of matching elements.
+     * Empty array if none found.
+     */
+    getByClassName: function (cl) {
+      var found = [];
+      var search = function (node, cl) {
+        if (node.classList.indexOf(cl) != -1) {
+          found.push(node);
+        } else if (node.children) {
+          node.children.forEach(function (child) {
+            search(child, cl);
+          });
+        }
+        return found;
+      };
+      return search(this, cl);
+    },
+
+    /**
+     * Recursively search for children of a specific type,
+     * e.g. Two.Polygon. Pass a reference to this type as the param.
+     * Returns an empty array if none found.
+     */
+    getByType: function(type) {
+      var found = [];
+      var search = function (node, type) {
+        for (var id in node.children) {
+          if (node.children[id] instanceof type) {
+            found.push(node.children[id]);
+          } else if (node.children[id] instanceof Two.Group) {
+            search(node.children[id], type);
+          }
+        }
+        return found;
+      };
+      return search(this, type);
+    },
+
+    /**
+     * Add objects to the group.
+     */
+    add: function(objects) {
+
+      // Allow to pass multiple objects either as array or as multiple arguments
+      // If it's an array also create copy of it in case we're getting passed
+      // a childrens array directly.
+      if (!(objects instanceof Array)) {
+        objects = _.toArray(arguments);
+      } else {
+        objects = objects.slice();
+      }
+
+      // Add the objects
+      for (var i = 0; i < objects.length; i++) {
+        if (!(objects[i] && objects[i].id)) continue;
+        this.children.push(objects[i]);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * Remove objects from the group.
+     */
+    remove: function(objects) {
+
+      var l = arguments.length,
+        grandparent = this.parent;
+
+      // Allow to call remove without arguments
+      // This will detach the object from the scene.
+      if (l <= 0 && grandparent) {
+        grandparent.remove(this);
+        return this;
+      }
+
+      // Allow to pass multiple objects either as array or as multiple arguments
+      // If it's an array also create copy of it in case we're getting passed
+      // a childrens array directly.
+      if (!(objects instanceof Array)) {
+        objects = _.toArray(arguments);
+      } else {
+        objects = objects.slice();
+      }
+
+      // Remove the objects
+      for (var i = 0; i < objects.length; i++) {
+        if (!objects[i] || !(this.children.ids[objects[i].id])) continue;
+        this.children.splice(_.indexOf(this.children, objects[i]), 1);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * Return an object with top, left, right, bottom, width, and height
+     * parameters of the group.
+     */
+    getBoundingClientRect: function(shallow) {
+      var rect;
+
+      // TODO: Update this to not __always__ update. Just when it needs to.
+      this._update(true);
+
+      // Variables need to be defined here, because of nested nature of groups.
+      var left = Infinity, right = -Infinity,
+          top = Infinity, bottom = -Infinity;
+
+      this.children.forEach(function(child) {
+
+        if (/(linear-gradient|radial-gradient|gradient)/.test(child._renderer.type)) {
+          return;
+        }
+
+        rect = child.getBoundingClientRect(shallow);
+
+        if (!_.isNumber(rect.top)   || !_.isNumber(rect.left)   ||
+            !_.isNumber(rect.right) || !_.isNumber(rect.bottom)) {
+          return;
+        }
+
+        top = min(rect.top, top);
+        left = min(rect.left, left);
+        right = max(rect.right, right);
+        bottom = max(rect.bottom, bottom);
+
+      }, this);
+
+      return {
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        width: right - left,
+        height: bottom - top
+      };
+
+    },
+
+    /**
+     * Trickle down of noFill
+     */
+    noFill: function() {
+      this.children.forEach(function(child) {
+        child.noFill();
+      });
+      return this;
+    },
+
+    /**
+     * Trickle down of noStroke
+     */
+    noStroke: function() {
+      this.children.forEach(function(child) {
+        child.noStroke();
+      });
+      return this;
+    },
+
+    /**
+     * Trickle down subdivide
+     */
+    subdivide: function() {
+      var args = arguments;
+      this.children.forEach(function(child) {
+        child.subdivide.apply(child, args);
+      });
+      return this;
+    },
+
+    flagReset: function() {
+
+      if (this._flagAdditions) {
+        this.additions.length = 0;
+        this._flagAdditions = false;
+      }
+
+      if (this._flagSubtractions) {
+        this.subtractions.length = 0;
+        this._flagSubtractions = false;
+      }
+
+      this._flagOrder = this._flagMask = this._flagOpacity = false;
+
+      Two.Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Group.MakeObservable(Group.prototype);
+
+  /**
+   * Helper function used to sync parent-child relationship within the
+   * `Two.Group.children` object.
+   *
+   * Set the parent of the passed object to another object
+   * and updates parent-child relationships
+   * Calling with one arguments will simply remove the parenting
+   */
+  function replaceParent(child, newParent) {
+
+    var parent = child.parent;
+    var index;
+
+    if (parent === newParent) {
+      this.additions.push(child);
+      this._flagAdditions = true;
+      return;
+    }
+
+    if (parent && parent.children.ids[child.id]) {
+
+      index = _.indexOf(parent.children, child);
+      parent.children.splice(index, 1);
+
+      // If we're passing from one parent to another...
+      index = _.indexOf(parent.additions, child);
+
+      if (index >= 0) {
+        parent.additions.splice(index, 1);
+      } else {
+        parent.subtractions.push(child);
+        parent._flagSubtractions = true;
+      }
+
+    }
+
+    if (newParent) {
+      child.parent = newParent;
+      this.additions.push(child);
+      this._flagAdditions = true;
+      return;
+    }
+
+    // If we're passing from one parent to another...
+    index = _.indexOf(this.additions, child);
+
+    if (index >= 0) {
+      this.additions.splice(index, 1);
+    } else {
+      this.subtractions.push(child);
+      this._flagSubtractions = true;
+    }
+
+    delete child.parent;
+
+  }
+
+})((typeof global !== 'undefined' ? global : this).Two);
diff --git a/patches/32bit-avoid-overloading.patch b/patches/32bit-avoid-overloading.patch
new file mode 100644 (file)
index 0000000..7d906ca
--- /dev/null
@@ -0,0 +1,23 @@
+Description: Avoid overloading on 32 bit architectures
+ unsigned and size_t are equivalent on 32 bit architectures,
+ so only define the size_t based overload of advance on 64
+ bit architectures.
+ https://wiki.debian.org/ArchitectureSpecificsMemo
+Author: James Page <james.page@ubuntu.com>, Bernd Zeimetz <bzed@debian.org>
+Forwarded: no
+
+--- a/src/include/buffer.h
++++ b/src/include/buffer.h
+@@ -737,7 +737,12 @@ inline namespace v14_2_0 {
+       void advance(int o) = delete;
+       void advance(unsigned o);
++
++// unsigned and size_t are equivalent on 32bit architectures.
++// so casting is only needed when not on 32bit.
++#if defined(UINTPTR_MAX) && UINTPTR_MAX > 0xffffffff
+       void advance(size_t o) { advance(static_cast<unsigned>(o)); }
++#endif
+       void seek(unsigned o);
+       char operator*() const;
+       iterator_impl& operator++();
diff --git a/patches/32bit-avoid-size_t.patch b/patches/32bit-avoid-size_t.patch
new file mode 100644 (file)
index 0000000..989ea9a
--- /dev/null
@@ -0,0 +1,112 @@
+Description: Avoid use of size_t when necessary
+ On 32 bit architectures size_t is not a 64 bit type, which
+ causes comparison mismatch failures during compilation.
+Author: James Page <james.page@ubuntu.com>, Bernd Zeimetz <bzed@debian.org>
+Forwarded: no
+
+Index: ceph/src/osd/PrimaryLogPG.cc
+===================================================================
+--- ceph.orig/src/osd/PrimaryLogPG.cc
++++ ceph/src/osd/PrimaryLogPG.cc
+@@ -1611,7 +1611,7 @@ int PrimaryLogPG::do_scrub_ls(MOSDOp *m,
+ void PrimaryLogPG::calc_trim_to()
+ {
+-  size_t target = cct->_conf->osd_min_pg_log_entries;
++  uint64_t target = cct->_conf->osd_min_pg_log_entries;
+   if (is_degraded() ||
+       state_test(PG_STATE_RECOVERING |
+                  PG_STATE_RECOVERY_WAIT |
+@@ -1627,15 +1627,15 @@ void PrimaryLogPG::calc_trim_to()
+   if (limit != eversion_t() &&
+       limit != pg_trim_to &&
+       pg_log.get_log().approx_size() > target) {
+-    size_t num_to_trim = std::min(pg_log.get_log().approx_size() - target,
+-                             cct->_conf->osd_pg_log_trim_max);
++    uint64_t num_to_trim = std::min(pg_log.get_log().approx_size() - target,
++                                    cct->_conf->osd_pg_log_trim_max);
+     if (num_to_trim < cct->_conf->osd_pg_log_trim_min &&
+         cct->_conf->osd_pg_log_trim_max >= cct->_conf->osd_pg_log_trim_min) {
+       return;
+     }
+     list<pg_log_entry_t>::const_iterator it = pg_log.get_log().log.begin();
+     eversion_t new_trim_to;
+-    for (size_t i = 0; i < num_to_trim; ++i) {
++    for (uint64_t i = 0; i < num_to_trim; ++i) {
+       new_trim_to = it->version;
+       ++it;
+       if (new_trim_to > limit) {
+Index: ceph/src/os/bluestore/BlueFS.h
+===================================================================
+--- ceph.orig/src/os/bluestore/BlueFS.h
++++ ceph/src/os/bluestore/BlueFS.h
+@@ -69,7 +69,7 @@ public:
+    * @params
+    * alloc_size - allocation unit size to check
+    */
+-  virtual size_t available_freespace(uint64_t alloc_size) = 0;
++  virtual uint64_t available_freespace(uint64_t alloc_size) = 0;
+ };
+ class BlueFSVolumeSelector {
+Index: ceph/src/os/bluestore/BlueStore.cc
+===================================================================
+--- ceph.orig/src/os/bluestore/BlueStore.cc
++++ ceph/src/os/bluestore/BlueStore.cc
+@@ -5911,12 +5911,12 @@ int BlueStore::allocate_bluefs_freespace
+   return 0;
+ }
+-size_t BlueStore::available_freespace(uint64_t alloc_size) {
+-  size_t total = 0;
+-  auto iterated_allocation = [&](size_t off, size_t len) {
++uint64_t BlueStore::available_freespace(uint64_t alloc_size) {
++  uint64_t total = 0;
++  auto iterated_allocation = [&](uint64_t off, uint64_t len) {
+     //only count in size that is alloc_size aligned
+-    size_t dist_to_alignment;
+-    size_t offset_in_block = off & (alloc_size - 1);
++    uint64_t dist_to_alignment;
++    uint64_t offset_in_block = off & (alloc_size - 1);
+     if (offset_in_block == 0)
+       dist_to_alignment = 0;
+     else
+Index: ceph/src/os/bluestore/BlueStore.h
+===================================================================
+--- ceph.orig/src/os/bluestore/BlueStore.h
++++ ceph/src/os/bluestore/BlueStore.h
+@@ -3081,7 +3081,7 @@ private:
+     PExtentVector& extents) override {
+     return allocate_bluefs_freespace(min_size, size, &extents);
+   };
+-  size_t available_freespace(uint64_t alloc_size) override;
++  uint64_t available_freespace(uint64_t alloc_size) override;
+ public:
+   struct sb_info_t {
+Index: ceph/src/common/config_values.h
+===================================================================
+--- ceph.orig/src/common/config_values.h
++++ ceph/src/common/config_values.h
+@@ -50,7 +50,7 @@ public:
+ #define OPTION_OPT_U32(name) uint64_t name;
+ #define OPTION_OPT_U64(name) uint64_t name;
+ #define OPTION_OPT_UUID(name) uuid_d name;
+-#define OPTION_OPT_SIZE(name) size_t name;
++#define OPTION_OPT_SIZE(name) uint64_t name;
+ #define OPTION(name, ty)       \
+   public:                      \
+     OPTION_##ty(name)          
+Index: ceph/src/common/options.cc
+===================================================================
+--- ceph.orig/src/common/options.cc
++++ ceph/src/common/options.cc
+@@ -189,7 +189,7 @@ int Option::parse_value(
+     }
+     *out = uuid;
+   } else if (type == Option::TYPE_SIZE) {
+-    Option::size_t sz{strict_iecstrtoll(val.c_str(), error_message)};
++    Option::size_t sz{static_cast<std::size_t>(strict_iecstrtoll(val.c_str(), error_message))};
+     if (!error_message->empty()) {
+       return -EINVAL;
+     }
diff --git a/patches/add-option-to-disable-ceph-dencoder.patch b/patches/add-option-to-disable-ceph-dencoder.patch
new file mode 100644 (file)
index 0000000..a9cf205
--- /dev/null
@@ -0,0 +1,11 @@
+Index: ceph/src/tools/CMakeLists.txt
+===================================================================
+--- ceph.orig/src/tools/CMakeLists.txt
++++ ceph/src/tools/CMakeLists.txt
+@@ -126,4 +126,6 @@ if(WITH_RBD)
+   endif()
+ endif(WITH_RBD)
++if(NOT DISABLE_DENCODER)
+ add_subdirectory(ceph-dencoder)
++endif(DISABLE_DENCODER)
diff --git a/patches/another-cmakelists-fix.patch b/patches/another-cmakelists-fix.patch
new file mode 100644 (file)
index 0000000..3b2f007
--- /dev/null
@@ -0,0 +1,31 @@
+Description: Another cmakelists fix
+ This fixes the last Boost 1.74 compatibility problems.
+Author: Thomas Goirand <zigo@debian.org>
+Forwarded: no
+Last-Update: 2021-01-08
+
+--- ceph-14.2.15.orig/CMakeLists.txt
++++ ceph-14.2.15/CMakeLists.txt
+@@ -27,6 +27,9 @@ endif()
+ if(POLICY CMP0075)
+   cmake_policy(SET CMP0075 NEW)
+ endif()
++if(POLICY CMP0093)
++  cmake_policy(SET CMP0093 NEW)
++endif()
+ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/")
+ if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+--- ceph-14.2.15.orig/src/rgw/CMakeLists.txt
++++ ceph-14.2.15/src/rgw/CMakeLists.txt
+@@ -19,6 +19,10 @@ function(gperf_generate input output)
+     )
+ endfunction()
++if(Boost_VERSION VERSION_GREATER_EQUAL 1.74)
++  add_definitions(-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT)
++endif()
++
+ set(librgw_common_srcs
+   services/svc_finisher.cc
+   services/svc_notify.cc
diff --git a/patches/bluefs-use-uint64_t-for-len.patch b/patches/bluefs-use-uint64_t-for-len.patch
new file mode 100644 (file)
index 0000000..1bf59d0
--- /dev/null
@@ -0,0 +1,58 @@
+From 10a953afc8f803e50c96354470fb114b33e62599 Mon Sep 17 00:00:00 2001
+From: Kefu Chai <kchai@redhat.com>
+Date: Fri, 28 Jun 2019 11:35:54 +0800
+Subject: [PATCH] os/bluestore/BlueFS: use uint64_t for `len`
+
+change the type of parameter `len` of `BlueFS::_read_random()` from
+`size_t` to `uint64_t`.
+
+i think the type of `size_t` comes from
+`rocksdb::RandomAccessFile::Read(uint64_t offset, size_t n,
+rocksdb::Slice* result, char* scratch)`. and when we implement this
+method, we continued using `n`'s type. but, we are using it with
+`std::min()`, for instance, where the template parameter type deduction
+fails if the lhs and rhs parameters' types are different. so probaly the
+better solution is to use `uint64_t` directly to avoid the the cast and
+specializing the template.
+
+Signed-off-by: Kefu Chai <kchai@redhat.com>
+---
+ src/os/bluestore/BlueFS.cc | 4 ++--
+ src/os/bluestore/BlueFS.h  | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+Index: ceph/src/os/bluestore/BlueFS.cc
+===================================================================
+--- ceph.orig/src/os/bluestore/BlueFS.cc
++++ ceph/src/os/bluestore/BlueFS.cc
+@@ -1526,7 +1526,7 @@ void BlueFS::_drop_link(FileRef file)
+ int BlueFS::_read_random(
+   FileReader *h,         ///< [in] read from here
+   uint64_t off,          ///< [in] offset
+-  size_t len,            ///< [in] this many bytes
++  uint64_t len,          ///< [in] this many bytes
+   char *out)             ///< [out] optional: or copy it here
+ {
+   auto* buf = &h->buf;
+@@ -1556,7 +1556,7 @@ int BlueFS::_read_random(
+       s_lock.unlock();
+       uint64_t x_off = 0;
+       auto p = h->file->fnode.seek(off, &x_off);
+-      uint64_t l = std::min(p->length - x_off, static_cast<uint64_t>(len));
++      uint64_t l = std::min(p->length - x_off, len);
+       dout(20) << __func__ << " read random 0x"
+              << std::hex << x_off << "~" << l << std::dec
+              << " of " << *p << dendl;
+Index: ceph/src/os/bluestore/BlueFS.h
+===================================================================
+--- ceph.orig/src/os/bluestore/BlueFS.h
++++ ceph/src/os/bluestore/BlueFS.h
+@@ -412,7 +412,7 @@ private:
+   int _read_random(
+     FileReader *h,   ///< [in] read from here
+     uint64_t offset, ///< [in] offset
+-    size_t len,      ///< [in] this many bytes
++    uint64_t len,    ///< [in] this many bytes
+     char *out);      ///< [out] optional: or copy it here
+   void _invalidate_cache(FileRef f, uint64_t offset, uint64_t length);
diff --git a/patches/civetweb-755-1.8-somaxconn-configurable.patch b/patches/civetweb-755-1.8-somaxconn-configurable.patch
new file mode 100644 (file)
index 0000000..8313b3e
--- /dev/null
@@ -0,0 +1,53 @@
+Description: Makes SOMAXCONN user-configurable.
+Author: Jesse Williamson <jesse.williamson@canonical.com>
+Origin: upstream, https://github.com/civetweb/civetweb/pull/776/commits/febab7dc38c9671577603425c54c20f841e27f97
+Bug: https://github.com/civetweb/civetweb/issues/775
+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1838109
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+--- a/src/civetweb/src/civetweb.c
++++ b/src/civetweb/src/civetweb.c
+@@ -1541,10 +1541,6 @@ typedef int socklen_t;
+ #define MSG_NOSIGNAL (0)
+ #endif
+-#if !defined(SOMAXCONN)
+-#define SOMAXCONN (100)
+-#endif
+-
+ /* Size of the accepted socket queue */
+ #if !defined(MGSQLEN)
+ #define MGSQLEN (20)
+@@ -2063,6 +2059,7 @@ enum {
+       SSL_CERTIFICATE,
+       SSL_CERTIFICATE_CHAIN,
+       NUM_THREADS,
++      SO_MAX_CONNECTIONS,
+       RUN_AS_USER,
+       URL_REWRITE_PATTERN,
+       HIDE_FILES,
+@@ -2165,6 +2162,7 @@ static struct mg_option config_options[]
+     {"ssl_certificate", CONFIG_TYPE_FILE, NULL},
+     {"ssl_certificate_chain", CONFIG_TYPE_FILE, NULL},
+     {"num_threads", CONFIG_TYPE_NUMBER, "50"},
++    {"max_connections", CONFIG_TYPE_NUMBER, "100"},
+     {"run_as_user", CONFIG_TYPE_STRING, NULL},
+     {"url_rewrite_patterns", CONFIG_TYPE_STRING_LIST, NULL},
+     {"hide_files_patterns", CONFIG_TYPE_EXT_PATTERN, NULL},
+@@ -13340,7 +13338,15 @@ set_ports_option(struct mg_context *ctx)
+                       continue;
+               }
+-              if (listen(so.sock, SOMAXCONN) != 0) {
++              char *p = ctx->config[SO_MAX_CONNECTIONS];
++              long opt_max_connections = strtol(p, NULL, 10);
++              if(opt_max_connections > INT_MAX || opt_max_connections < 1) {
++                      mg_cry(fc(ctx),
++                             "max_connections value \"%s\" is invalid", p);
++                      continue;
++              }
++
++              if (listen(so.sock, (int)opt_max_connections) != 0) {
+                       mg_cry(fc(ctx),
+                              "cannot listen to %.*s: %d (%s)",
diff --git a/patches/civetweb-755-1.8-somaxconn-configurable_conf.patch b/patches/civetweb-755-1.8-somaxconn-configurable_conf.patch
new file mode 100644 (file)
index 0000000..e8e3b0b
--- /dev/null
@@ -0,0 +1,18 @@
+Description: Adds max_connections to reference configuration.
+Author: Jesse Williamson <jesse.williamson@canonical.com>
+Origin: upstream, https://github.com/civetweb/civetweb/pull/776/commits/3b8eb36676f70d06f8918ccf62029207c49cdda0
+Bug: https://github.com/civetweb/civetweb/issues/775
+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1838109
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+--- a/src/civetweb/resources/civetweb.conf
++++ b/src/civetweb/resources/civetweb.conf
+@@ -8,6 +8,8 @@
+ document_root .
+ listening_ports 8080
++#so_max_connections 100
++
+ # cgi_pattern **.cgi$|**.pl$|**.php$
+ # cgi_environment 
+ # put_delete_auth_file 
diff --git a/patches/civetweb-755-1.8-somaxconn-configurable_test.patch b/patches/civetweb-755-1.8-somaxconn-configurable_test.patch
new file mode 100644 (file)
index 0000000..cdd27b5
--- /dev/null
@@ -0,0 +1,17 @@
+Description: Adds max_connections to test display.
+Author: Jesse Williamson <jesse.williamson@canonical.com>
+Origin: upstream, https://github.com/civetweb/civetweb/pull/776/commits/3b8eb36676f70d06f8918ccf62029207c49cdda0
+Bug: https://github.com/civetweb/civetweb/issues/775
+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1838109
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+--- a/src/civetweb/test/page3.ssjs
++++ b/src/civetweb/test/page3.ssjs
+@@ -21,6 +21,7 @@ opts = [
+ "document_root",
+ "ssl_certificate",
+ "num_threads",
++"max_connections",
+ "run_as_user",
+ "url_rewrite_patterns",
+ "hide_files_patterns",
diff --git a/patches/cmake_add_1.74_to_known_versions.patch b/patches/cmake_add_1.74_to_known_versions.patch
new file mode 100644 (file)
index 0000000..0047889
--- /dev/null
@@ -0,0 +1,52 @@
+Description: cmake: add 1.74 to known versions
+Author: Kefu Chai <kchai@redhat.com>
+Bug-Debian: https://bugs.debian.org/977243
+Origin: upstream, https://github.com/ceph/ceph/commit/b6a94da6149e50bdd43752919d7c01b04c59f79e.patch
+Last-Update: 2020-12-13
+
+--- ceph-14.2.15.orig/cmake/modules/FindBoost.cmake
++++ ceph-14.2.15/cmake/modules/FindBoost.cmake
+@@ -437,10 +437,23 @@ if (NOT Boost_NO_BOOST_CMAKE)
+     endif()
+   endif()
++  set(_boost_FIND_PACKAGE_ARGS "")
++  if(Boost_NO_SYSTEM_PATHS)
++    list(APPEND _boost_FIND_PACKAGE_ARGS NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH)
++  endif()
++
+   # Do the same find_package call but look specifically for the CMake version.
+   # Note that args are passed in the Boost_FIND_xxxxx variables, so there is no
+   # need to delegate them to this find_package call.
+-  find_package(Boost QUIET NO_MODULE)
++  cmake_policy(PUSH)
++  if(BOOST_ROOT AND NOT Boost_ROOT)
++    if(POLICY CMP0074)
++      cmake_policy(SET CMP0074 NEW)
++    endif()
++    set(Boost_ROOT "${BOOST_ROOT}")
++  endif()
++  find_package(Boost QUIET NO_MODULE ${_boost_FIND_PACKAGE_ARGS})
++  cmake_policy(POP)
+   mark_as_advanced(Boost_DIR)
+   # If we found a boost cmake package, then we're done. Print out what we found.
+@@ -1157,7 +1170,7 @@ function(_Boost_COMPONENT_DEPENDENCIES c
+     set(_Boost_TIMER_DEPENDENCIES chrono)
+     set(_Boost_WAVE_DEPENDENCIES filesystem serialization thread chrono date_time atomic)
+     set(_Boost_WSERIALIZATION_DEPENDENCIES serialization)
+-    if(NOT Boost_VERSION_STRING VERSION_LESS 1.73.0)
++    if(NOT Boost_VERSION_STRING VERSION_LESS 1.75.0)
+       message(WARNING "New Boost version may have incorrect or missing dependencies and imported targets")
+     endif()
+   endif()
+@@ -1429,7 +1442,8 @@ else()
+   # _Boost_COMPONENT_HEADERS.  See the instructions at the top of
+   # _Boost_COMPONENT_DEPENDENCIES.
+   set(_Boost_KNOWN_VERSIONS ${Boost_ADDITIONAL_VERSIONS}
+-    "1.72.0" "1.72" "1.71.0" "1.71" "1.70.0" "1.70" "1.69.0" "1.69"
++    "1.74.0" "1.74"
++    "1.73.0" "1.73" "1.72.0" "1.72" "1.71.0" "1.71" "1.70.0" "1.70" "1.69.0" "1.69"
+     "1.68.0" "1.68" "1.67.0" "1.67" "1.66.0" "1.66" "1.65.1" "1.65.0" "1.65"
+     "1.64.0" "1.64" "1.63.0" "1.63" "1.62.0" "1.62" "1.61.0" "1.61" "1.60.0" "1.60"
+     "1.59.0" "1.59" "1.58.0" "1.58" "1.57.0" "1.57" "1.56.0" "1.56" "1.55.0" "1.55"
diff --git a/patches/cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost.Asio_users.patch b/patches/cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost.Asio_users.patch
new file mode 100644 (file)
index 0000000..b2bc978
--- /dev/null
@@ -0,0 +1,40 @@
+Description: cmake: define BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT for
+ Boost.Asio users
+ .
+ see also
+ https://www.boost.org/doc/libs/1_74_0/doc/html/boost_asio/std_executors.html#boost_asio.std_executors.polymorphic_i_o_executor
+ .
+ we could use `asio::any_io_executor` later on though for better
+ performance.
+ .
+ also, define CMP0093, so FindBoost reports Boost_VERSION in x.y.z
+ format. it is simpler to use `VERSION_GREATER_EQUAL` to compare its
+ version with 1.74 instead of its C macro version ("107000").
+Signed-off-by: Kefu Chai <kchai@redhat.com>
+Author: Kefu Chai <kchai@redhat.com>
+Origin: upstream, https://github.com/ceph/ceph/commit/3d708219092d0e89a1434c30ffc8a4999f062cc0.patch
+Bug-Debian: https://bugs.debian.org/977243
+Last-Update: 2020-12-13
+
+--- ceph-14.2.15.orig/src/librbd/CMakeLists.txt
++++ ceph-14.2.15/src/librbd/CMakeLists.txt
+@@ -1,3 +1,7 @@
++if(Boost_VERSION VERSION_GREATER_EQUAL 1.74)
++  add_definitions(-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT)
++endif()
++
+ add_library(rbd_types STATIC
+   journal/Types.cc
+   mirroring_watcher/Types.cc
+--- ceph-14.2.15.orig/CMakeLists.txt   2020-12-14 09:42:50.543215302 +0100
++++ ceph-14.2.15/CMakeLists.txt        2020-12-14 09:44:07.827084724 +0100
+@@ -21,6 +21,9 @@
+ if(POLICY CMP0051)
+   cmake_policy(SET CMP0051 NEW)
+ endif()
++if(POLICY CMP0074)
++  cmake_policy(SET CMP0074 NEW)
++endif()
+ if(POLICY CMP0075)
+   cmake_policy(SET CMP0075 NEW)
+ endif()
diff --git a/patches/debian-armel-armhf-buildflags.patch b/patches/debian-armel-armhf-buildflags.patch
new file mode 100644 (file)
index 0000000..e9a450a
--- /dev/null
@@ -0,0 +1,45 @@
+--- a/cmake/modules/SIMDExt.cmake
++++ b/cmake/modules/SIMDExt.cmake
+@@ -40,11 +40,14 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM")
+   set(HAVE_ARM 1)
+-  CHECK_C_COMPILER_FLAG(-mfpu=neon HAVE_ARM_NEON)
+-  if(HAVE_ARM_NEON)
+-    set(SIMD_COMPILE_FLAGS "${SIMD_COMPILE_FLAGS} -mfpu=neon")
++  if(CMAKE_LIBRARY_ARCHITECTURE EQUAL "arm-linux-gnueabi")
++    set(SIMD_COMPILE_FLAGS "${SIMD_COMPILE_FLAGS} --with-arch=armv5te --with-float=soft")
++  else()
++    CHECK_C_COMPILER_FLAG(-mfpu=neon HAVE_ARM_NEON)
++    if(HAVE_ARM_NEON)
++      set(SIMD_COMPILE_FLAGS "${SIMD_COMPILE_FLAGS} -mfpu=neon")
++    endif()
+   endif()
+-
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686|amd64|x86_64|AMD64")
+   set(HAVE_INTEL 1)
+   if(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|amd64|x86_64|AMD64")
+--- a/src/erasure-code/jerasure/gf-complete/m4/ax_ext.m4
++++ b/src/erasure-code/jerasure/gf-complete/m4/ax_ext.m4
+@@ -19,10 +19,17 @@ AC_DEFUN([AX_EXT],
+       ;;
+     arm*)
+-      AC_CACHE_CHECK([whether NEON is enabled], [ax_cv_have_neon_ext], [ax_cv_have_neon_ext=yes])
+-      if test "$ax_cv_have_neon_ext" = yes; then
+-        AX_CHECK_COMPILE_FLAG(-mfpu=neon, [SIMD_FLAGS="$SIMD_FLAGS -mfpu=neon -DARM_NEON"], [ax_cv_have_neon_ext=no])
+-      fi
++      case $host_cpu in
++          arm-linux-gnueabi)
++            AX_CHECK_COMPILE_FLAG(-mfpu=soft, [SIMD_FLAGS="$SIMD_FLAGS -mfpu=soft -march=armv5te"], [ax_cv_have_neon_ext=no])
++          ;;
++          *)
++            AC_CACHE_CHECK([whether NEON is enabled], [ax_cv_have_neon_ext], [ax_cv_have_neon_ext=yes])
++            if test "$ax_cv_have_neon_ext" = yes; then
++              AX_CHECK_COMPILE_FLAG(-mfpu=neon, [SIMD_FLAGS="$SIMD_FLAGS -mfpu=neon -DARM_NEON"], [ax_cv_have_neon_ext=no])
++            fi
++          ;;
++      esac
+       ;;
+     powerpc*)
diff --git a/patches/disable-crypto.patch b/patches/disable-crypto.patch
new file mode 100644 (file)
index 0000000..7cd1268
--- /dev/null
@@ -0,0 +1,16 @@
+Index: ceph/src/os/CMakeLists.txt
+===================================================================
+--- ceph.orig/src/os/CMakeLists.txt
++++ ceph/src/os/CMakeLists.txt
+@@ -110,8 +110,9 @@ endif()
+ target_link_libraries(os kv)
+ add_dependencies(os compressor_plugins)
+-add_dependencies(os crypto_plugins)
+-
++if(HAVE_INTEL AND HAVE_BETTER_YASM_ELF64 AND (NOT APPLE))
++    add_dependencies(os crypto_plugins)
++endif()
+ if(WITH_BLUESTORE)
+   add_executable(ceph-bluestore-tool
diff --git a/patches/fix-bash-completion-location b/patches/fix-bash-completion-location
new file mode 100644 (file)
index 0000000..916ff8a
--- /dev/null
@@ -0,0 +1,9 @@
+--- a/src/bash_completion/CMakeLists.txt
++++ b/src/bash_completion/CMakeLists.txt
+@@ -11,5 +11,5 @@ if(WITH_RADOSGW)
+ endif()
+ install(FILES ${completions}
+-  DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/bash_completion.d)
++  DESTINATION /usr/share/bash-completion/completions)
diff --git a/patches/make-ceph-python-3.9-aware.patch b/patches/make-ceph-python-3.9-aware.patch
new file mode 100644 (file)
index 0000000..67e55ea
--- /dev/null
@@ -0,0 +1,28 @@
+Description: Make Ceph Python 3.9 aware
+ Add versions of interpreters Ceph didn't know about.
+Author: Thomas Goirand <zigo@debian.org>
+Forwarded: no
+Last-Update: 2020-11-28
+
+--- ceph-14.2.15.orig/cmake/modules/FindPython3Interp.cmake
++++ ceph-14.2.15/cmake/modules/FindPython3Interp.cmake
+@@ -69,7 +69,7 @@
+ unset(_Python3_NAMES)
+-set(_PYTHON3_VERSIONS 3.6 3.5 3.4 3.3 3.2 3.1 3.0)
++set(_PYTHON3_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0)
+ if(Python3Interp_FIND_VERSION)
+     if(Python3Interp_FIND_VERSION_COUNT GREATER 1)
+--- ceph-14.2.15.orig/cmake/modules/FindPython3Libs.cmake
++++ ceph-14.2.15/cmake/modules/FindPython3Libs.cmake
+@@ -101,7 +101,7 @@ endif()
+ # To avoid picking up the system Python.h pre-maturely.
+ set(CMAKE_FIND_FRAMEWORK LAST)
+-set(_PYTHON3_VERSIONS 3.6 3.5 3.4 3.3 3.2 3.1 3.0)
++set(_PYTHON3_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0)
+ if(Python3Libs_FIND_VERSION)
+     if(Python3Libs_FIND_VERSION_COUNT GREATER 1)
diff --git a/patches/mds-purgequeue-use_uint64_t.patch b/patches/mds-purgequeue-use_uint64_t.patch
new file mode 100644 (file)
index 0000000..8797da2
--- /dev/null
@@ -0,0 +1,31 @@
+Index: ceph/src/mds/PurgeQueue.cc
+===================================================================
+--- ceph.orig/src/mds/PurgeQueue.cc
++++ ceph/src/mds/PurgeQueue.cc
+@@ -499,7 +499,7 @@ void PurgeQueue::_execute_item(
+   in_flight[expire_to] = item;
+   logger->set(l_pq_executing, in_flight.size());
+-  files_high_water = std::max(files_high_water, in_flight.size());
++  files_high_water = std::max(files_high_water, static_cast<uint64_t>(in_flight.size()));
+   logger->set(l_pq_executing_high_water, files_high_water);
+   auto ops = _calculate_ops(item);
+   ops_in_flight += ops;
+@@ -577,7 +577,7 @@ void PurgeQueue::_execute_item(
+     logger->set(l_pq_executing_ops_high_water, ops_high_water);
+     in_flight.erase(expire_to);
+     logger->set(l_pq_executing, in_flight.size());
+-    files_high_water = std::max(files_high_water, in_flight.size());
++    files_high_water = std::max(files_high_water, static_cast<uint64_t>(in_flight.size()));
+     logger->set(l_pq_executing_high_water, files_high_water);
+     return;
+   }
+@@ -654,7 +654,7 @@ void PurgeQueue::_execute_item_complete(
+   in_flight.erase(iter);
+   logger->set(l_pq_executing, in_flight.size());
+-  files_high_water = std::max(files_high_water, in_flight.size());
++  files_high_water = std::max(files_high_water, static_cast<uint64_t>(in_flight.size()));
+   logger->set(l_pq_executing_high_water, files_high_water);
+   dout(10) << "in_flight.size() now " << in_flight.size() << dendl;
diff --git a/patches/riscv64-link-pthread.patch b/patches/riscv64-link-pthread.patch
new file mode 100644 (file)
index 0000000..dd56cf2
--- /dev/null
@@ -0,0 +1,14 @@
+Description: Link with -pthread instead of -lpthread to fix FTBFS on riscv64
+Forwarded: no
+Last-Update: 2020-03-01
+
+--- ceph-14.2.7.orig/CMakeLists.txt
++++ ceph-14.2.7/CMakeLists.txt
+@@ -28,6 +28,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_S
+ if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+   set(LINUX ON)
++  set(THREADS_PREFER_PTHREAD_FLAG ON)
+   FIND_PACKAGE(Threads)
+ elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+   set(FREEBSD ON)
diff --git a/patches/series b/patches/series
new file mode 100644 (file)
index 0000000..ffd4270
--- /dev/null
@@ -0,0 +1,20 @@
+update-java-source-target-flags.patch
+disable-crypto.patch
+32bit-avoid-overloading.patch
+32bit-avoid-size_t.patch
+# Ubuntu: civetweb max connections
+civetweb-755-1.8-somaxconn-configurable_conf.patch
+civetweb-755-1.8-somaxconn-configurable.patch
+civetweb-755-1.8-somaxconn-configurable_test.patch
+# Upstream: py3
+# Upstream: 32bit
+bluefs-use-uint64_t-for-len.patch
+debian-armel-armhf-buildflags.patch
+fix-bash-completion-location
+add-option-to-disable-ceph-dencoder.patch
+riscv64-link-pthread.patch
+mds-purgequeue-use_uint64_t.patch
+make-ceph-python-3.9-aware.patch
+cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost.Asio_users.patch
+cmake_add_1.74_to_known_versions.patch
+another-cmakelists-fix.patch
diff --git a/patches/update-java-source-target-flags.patch b/patches/update-java-source-target-flags.patch
new file mode 100644 (file)
index 0000000..6c8b792
--- /dev/null
@@ -0,0 +1,23 @@
+Description: use --release 7 instead of -source/-target
+ Instead of -source/-target ceph should be build with --release for OpenJDK 9
+ or later so that the bootclasspath is also set, as per JEP-247, otherwise it
+ risks incurring into binary incompatibility when run with an earlier OpenJDK.
+ OpenJDK 11 minimum compatibility release has been updated to 7.
+Author: Tiago Stürmer Daitx <tiago.daitx@ubuntu.com>
+Bug-Ubuntu: https://launchpad.net/bugs/1756854
+Bug-Ubuntu: https://launchpad.net/bugs/1766998
+Forwarded: no
+Last-Update: 2018-04-24
+---
+
+--- a/src/java/CMakeLists.txt
++++ b/src/java/CMakeLists.txt
+@@ -21,7 +21,7 @@ set(java_srcs
+ #   warning: [options] bootstrap class path not set in conjunction with -source 1.7
+ # as per
+ #   https://blogs.oracle.com/darcy/entry/bootclasspath_older_source
+-set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8" "-Xlint:-options")
++set(CMAKE_JAVA_COMPILE_FLAGS "--release" "7" "-Xlint:-options")
+ set(jni_header_dir "${CMAKE_CURRENT_BINARY_DIR}/native")
+ if(CMAKE_VERSION VERSION_LESS 3.11)
+   set(CMAKE_JAVA_COMPILE_FLAGS ${CMAKE_JAVA_COMPILE_FLAGS} "-h" ${jni_header_dir})
diff --git a/python3-ceph-argparse.install b/python3-ceph-argparse.install
new file mode 100644 (file)
index 0000000..274b8b4
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/python3*/dist-packages/ceph_argparse.py
+usr/lib/python3*/dist-packages/ceph_daemon.py
diff --git a/python3-ceph.lintian-overrides b/python3-ceph.lintian-overrides
new file mode 100644 (file)
index 0000000..fd0d214
--- /dev/null
@@ -0,0 +1 @@
+python3-ceph: empty-binary-package
diff --git a/python3-cephfs.install b/python3-cephfs.install
new file mode 100644 (file)
index 0000000..6eb8836
--- /dev/null
@@ -0,0 +1,3 @@
+usr/lib/python3*/dist-packages/ceph_volume_client.py
+usr/lib/python3*/dist-packages/cephfs-*.egg-info
+usr/lib/python3*/dist-packages/cephfs.cpython*.so
diff --git a/python3-rados.install b/python3-rados.install
new file mode 100644 (file)
index 0000000..98b5d76
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/python3*/dist-packages/rados-*.egg-info
+usr/lib/python3*/dist-packages/rados.cpython*.so
diff --git a/python3-rbd.install b/python3-rbd.install
new file mode 100644 (file)
index 0000000..5f4e6e1
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/python3*/dist-packages/rbd-*.egg-info
+usr/lib/python3*/dist-packages/rbd.cpython*.so
diff --git a/python3-rgw.install b/python3-rgw.install
new file mode 100644 (file)
index 0000000..57f4559
--- /dev/null
@@ -0,0 +1,2 @@
+usr/lib/python3*/dist-packages/rgw-*.egg-info
+usr/lib/python3*/dist-packages/rgw.cpython*.so
diff --git a/rados-objclass-dev.install b/rados-objclass-dev.install
new file mode 100644 (file)
index 0000000..ac8f90e
--- /dev/null
@@ -0,0 +1 @@
+usr/include/rados/objclass.h
diff --git a/radosgw.dirs b/radosgw.dirs
new file mode 100644 (file)
index 0000000..a2f1849
--- /dev/null
@@ -0,0 +1 @@
+var/lib/ceph/radosgw
diff --git a/radosgw.install b/radosgw.install
new file mode 100644 (file)
index 0000000..329ea0e
--- /dev/null
@@ -0,0 +1,6 @@
+lib/systemd/system/ceph-radosgw*
+usr/bin/radosgw
+usr/bin/radosgw-es
+usr/bin/radosgw-object-expirer
+usr/bin/radosgw-token
+usr/share/man/man8/radosgw.8
diff --git a/radosgw.lintian-overrides b/radosgw.lintian-overrides
new file mode 100644 (file)
index 0000000..5c5e06b
--- /dev/null
@@ -0,0 +1,8 @@
+# Ceph upstart configuration's don't have init.d equivalents
+radosgw: init.d-script-not-marked-as-conffile etc/init.d/radosgw-all-starter
+radosgw: init.d-script-not-included-in-package etc/init.d/radosgw-all-starter
+radosgw: init.d-script-not-marked-as-conffile etc/init.d/radosgw-instance
+radosgw: init.d-script-not-included-in-package etc/init.d/radosgw-instance
+radosgw: init.d-script-not-marked-as-conffile etc/init.d/radosgw-all
+radosgw: init.d-script-not-included-in-package etc/init.d/radosgw-all
+radosgw: omitted-systemd-service-for-init.d-script radosgw
diff --git a/radosgw.postinst b/radosgw.postinst
new file mode 100644 (file)
index 0000000..3f15512
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -e
+
+if [ "${1}" = "configure" ] ; then
+       [ -f "/etc/default/ceph" ] && . /etc/default/ceph
+       [ -z "$SERVER_USER" ] && SERVER_USER=ceph
+       [ -z "$SERVER_GROUP" ] && SERVER_GROUP=ceph
+       if ! dpkg-statoverride --list /var/lib/ceph/radosgw >/dev/null; then
+               chown $SERVER_USER:$SERVER_GROUP /var/lib/ceph/radosgw
+       fi
+fi
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/radosgw.prerm b/radosgw.prerm
new file mode 100644 (file)
index 0000000..0288ab7
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+# vim: set noet ts=8:
+
+set -e
+
+case "$1" in
+    remove)
+       invoke-rc.d radosgw stop || {
+           RESULT=$?
+           if [ $RESULT != 100 ]; then
+               exit $RESULT
+           fi
+       }
+       ;;
+
+    *)
+       ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/rbd-fuse.install b/rbd-fuse.install
new file mode 100644 (file)
index 0000000..7b6b96f
--- /dev/null
@@ -0,0 +1,2 @@
+usr/bin/rbd-fuse
+usr/share/man/man8/rbd-fuse.8
diff --git a/rbd-mirror.install b/rbd-mirror.install
new file mode 100644 (file)
index 0000000..cc617a5
--- /dev/null
@@ -0,0 +1,3 @@
+lib/systemd/system/ceph-rbd-mirror*
+usr/bin/rbd-mirror
+usr/share/man/man8/rbd-mirror.8
diff --git a/rbd-nbd.install b/rbd-nbd.install
new file mode 100644 (file)
index 0000000..385c450
--- /dev/null
@@ -0,0 +1,2 @@
+usr/bin/rbd-nbd
+usr/share/man/man8/rbd-nbd.8
diff --git a/rest-bench.install b/rest-bench.install
new file mode 100644 (file)
index 0000000..8535f20
--- /dev/null
@@ -0,0 +1 @@
+usr/bin/rest-bench
diff --git a/rules b/rules
new file mode 100755 (executable)
index 0000000..c712fd5
--- /dev/null
+++ b/rules
@@ -0,0 +1,167 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+#export DH_VERBOSE=1
+
+DEB_HOST_ARCH_BITS ?= $(shell dpkg-architecture -qDEB_HOST_ARCH_BITS)
+export DEB_BUILD_ARCH      ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
+export DEB_HOST_ARCH      ?= $(shell dpkg-architecture -qDEB_HOST_ARCH)
+
+# support ccache for faster build
+# cmake uses /usr/bin/c*
+ifeq (yes,$(findstring yes,$(shell test -L /usr/lib/ccache/c++ && test -L /usr/lib/ccache/cc && echo -n yes)))
+  extraopts += -DWITH_CCACHE=ON
+endif
+
+# try to save even more memory on some architectures
+# see #849657 for hints.
+# Reduce size of debug symbols to fix FTBFS due to the
+# 2GB/3GB address space limits on 32bit
+ifeq (32,$(DEB_HOST_ARCH_BITS))
+        export DEB_CFLAGS_MAINT_APPEND = -g1
+        export DEB_CXXFLAGS_MAINT_APPEND = -g1
+endif
+
+# we don't have NEON on armel.
+ifeq ($(DEB_HOST_ARCH),armel)
+    extraopts += -DHAVE_ARM_NEON=0
+endif
+
+# disable ceph-dencoder on 32bit except i386 to avoid g++ oom
+ifneq (,$(filter $(DEB_HOST_ARCH), armel armhf hppa m68k mips mipsel powerpc sh4 x32))
+    extraopts += -DDISABLE_DENCODER=1
+endif
+
+ifeq ($(shell dpkg-vendor --is Ubuntu && echo yes) $(DEB_HOST_ARCH), yes i386)
+   skip_packages = -Nceph -Nceph-base -Nceph-mds -Nceph-mgr -Nceph-mon -Nceph-osd
+endif
+
+# minimise needless linking and link to libatomic
+# The last is needed because long long atomic operations are not directly
+# supported by all processor architectures
+export DEB_LDFLAGS_MAINT_APPEND= -Wl,--as-needed -latomic
+
+# Enable hardening
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+export DESTDIR=$(CURDIR)/debian/tmp
+
+export JAVA_HOME=/usr/lib/jvm/default-java
+## Set JAVAC to prevent FTBFS due to incorrect use of 'gcj' if found (see "m4/ac_prog_javac.m4").
+export JAVAC=javac
+
+extraopts += -DWITH_OCF=ON -DWITH_NSS=ON -DWITH_PYTHON3=ON -DWITH_DEBUG=ON
+extraopts += -DWITH_PYTHON2=OFF -DMGR_PYTHON_VERSION=3
+extraopts += -DWITH_CEPHFS_JAVA=ON
+extraopts += -DWITH_CEPHFS_SHELL=ON
+extraopts += -DWITH_TESTS=OFF
+extraopts += -DWITH_SYSTEM_BOOST=ON
+extraopts += -DWITH_LTTNG=OFF -DWITH_EMBEDDED=OFF
+extraopts += -DCMAKE_INSTALL_LIBEXECDIR=/usr/lib
+extraopts += -DWITH_MGR_DASHBOARD_FRONTEND=OFF
+extraopts += -DWITH_SYSTEMD=ON -DCEPH_SYSTEMD_ENV_DIR=/etc/default
+extraopts += -DCMAKE_INSTALL_SYSCONFDIR=/etc
+extraopts += -DCMAKE_INSTALL_SYSTEMD_SERVICEDIR=/lib/systemd/system
+
+ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
+  NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
+  extraopts += -DBOOST_J=$(NUMJOBS)
+endif
+
+ifneq (,$(filter $(DEB_HOST_ARCH),s390x mips64el ia64 m68k ppc64 riscv64 sh4 sparc64 x32 alpha))
+  # beast depends on libboost_{context,coroutine} which is not supported on s390x
+  extraopts += -DWITH_BOOST_CONTEXT=OFF
+else
+  extraopts += -DWITH_BOOST_CONTEXT=ON
+endif
+
+# Disable SPDK as it generates a build which is no compatible
+# with older CPU's which are still supported by Ubuntu.
+extraopts += -DWITH_SPDK=OFF
+
+MAX_PARALLEL ?= $(shell ./debian/calc-max-parallel.sh)
+
+%:
+       dh $@ --buildsystem=cmake --with javahelper,python3,systemd $(MAX_PARALLEL)
+
+override_dh_auto_configure:
+       env | sort
+       dh_auto_configure --buildsystem=cmake -- $(extraopts)
+
+override_dh_auto_install:
+       dh_auto_install --buildsystem=cmake --destdir=$(DESTDIR)
+       if [ ! -f $(DESTDIR)/usr/bin/ceph-dencoder ]; then \
+           cp debian/workarounds/ceph-dencoder-oom $(DESTDIR)/usr/bin/ceph-dencoder ;\
+           chmod 755 $(DESTDIR)/usr/bin/ceph-dencoder ;\
+       fi
+       install -D -m 644 udev/50-rbd.rules $(DESTDIR)/lib/udev/rules.d/50-rbd.rules
+       install -D -m 644 src/etc-rbdmap $(DESTDIR)/etc/ceph/rbdmap
+       install -D -m 644 etc/sysctl/90-ceph-osd.conf $(DESTDIR)/etc/sysctl.d/30-ceph-osd.conf
+       install -D -m 600 sudoers.d/ceph-osd-smartctl $(DESTDIR)/etc/sudoers.d/ceph-osd-smartctl
+       # NOTE: ensure that any versioned erasure coding test code is dropped
+       #       from the package install - package ships unversioned modules.
+       rm -f $(CURDIR)/debian/tmp/usr/lib/*/ceph/erasure-code/libec_*.so.*
+       find $(CURDIR)/debian/tmp/usr/lib/*/ceph/erasure-code -type l -delete || :
+
+
+# doc/changelog is a directory, which confuses dh_installchangelogs
+override_dh_installchangelogs:
+       dh_installchangelogs --exclude doc/changelog
+
+override_dh_installlogrotate:
+       cp src/logrotate.conf debian/ceph-common.logrotate
+       dh_installlogrotate -pceph-common
+
+
+override_dh_installinit:
+       cp src/init-radosgw debian/radosgw.init
+       dh_installinit --no-start
+       dh_installinit -pceph-common --name=rbdmap --no-start
+       dh_installinit -pceph-base --name ceph --no-start
+       # install the systemd stuff manually since we have funny service names
+       # and need to update the paths in all of the files post install
+       # systemd:ceph-common
+       install -d -m0755 debian/ceph-common/usr/lib/tmpfiles.d
+       install -m 0644 -D systemd/ceph.tmpfiles.d debian/ceph-common/usr/lib/tmpfiles.d/ceph.conf
+       # NOTE(jamespage): Install previous ceph-mon service from packaging for upgrades
+       install -d -m0755 debian/ceph-mon/lib/systemd/system
+       install -m0644 debian/lib-systemd/system/ceph-mon.service debian/ceph-mon/lib/systemd/system
+       # Ensure Debian/Ubuntu specific systemd units are NOT automatically enabled and started
+       # Enable systemd targets only
+       dh_systemd_enable -Xceph-mon.service -Xceph-osd.service -X ceph-mds.service
+       # Start systemd targets only
+       dh_systemd_start --no-stop-on-upgrade --no-restart-after-upgrade
+
+override_dh_systemd_enable:
+       # systemd enable done as part of dh_installinit
+
+override_dh_systemd_start:
+       # systemd start done as part of dh_installinit
+
+override_dh_makeshlibs:
+       # exclude jni libraries in libcephfs-jni to avoid pointless ldconfig
+       # calls in maintainer scripts; exclude private erasure-code plugins.
+       dh_makeshlibs -V -X/usr/lib/jni -X/usr/lib/$(DEB_HOST_MULTIARCH)/ceph/erasure-code
+
+override_dh_auto_test:
+       # do not run tests
+
+override_dh_shlibdeps:
+       dh_shlibdeps -a --exclude=erasure-code --exclude=rados-classes --exclude=compressor
+
+override_dh_python3:
+       for binding in rados cephfs rbd rgw; do \
+               dh_python3 -p python3-$$binding --shebang=/usr/bin/python3;      \
+        done
+       dh_python3 -p python3-ceph-argparse --shebang=/usr/bin/python3
+       dh_python3 -p ceph-common --shebang=/usr/bin/python3
+       dh_python3 -p ceph-base --shebang=/usr/bin/python3
+       dh_python3 -p ceph-osd --shebang=/usr/bin/python3
+       dh_python3 -p ceph-mgr --shebang=/usr/bin/python3
+       dh_python3 -p cephfs-shell --shebang=/usr/bin/python3
+
+override_dh_builddeb:
+       dh_builddeb ${skip_packages}
+
+override_dh_gencontrol:
+       dh_gencontrol ${skip_packages}
+
diff --git a/source.lintian-overrides b/source.lintian-overrides
new file mode 100644 (file)
index 0000000..6a4dc1a
--- /dev/null
@@ -0,0 +1,3 @@
+# This is a false positive: upstream is shipping both the compiled
+# and the source version of the .js files.
+ceph source: source-is-missing
diff --git a/source/format b/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/source/lintian-overrides b/source/lintian-overrides
new file mode 100644 (file)
index 0000000..f3cb221
--- /dev/null
@@ -0,0 +1,11 @@
+# we don't ship thse thirdparty libs here, afaics.
+# license.txt is outdated but fixing it causes the same error
+# come up for the patch, so...
+ceph source: license-problem-json-evil src/rapidjson/license.txt
+
+# pybind js source is in src/pybind/mgr/dashboard/frontend/src
+ceph source: source-is-missing src/pybind/mgr/dashboard/frontend/dist/*.js
+
+# regression test file is actually shipped with source and build files
+ceph source: source-contains-prebuilt-ms-help-file src/boost/tools/boost_install/test/iostreams/zlib-1.2.11/contrib/dotzlib/DotZLib.chm
+ceph source: source-contains-prebuilt-ms-help-file src/boost/libs/beast/test/extern/zlib-1.2.11/contrib/dotzlib/DotZLib.chm
diff --git a/source/options b/source/options
new file mode 100644 (file)
index 0000000..d029bb0
--- /dev/null
@@ -0,0 +1,11 @@
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googlemock/msvc/20\d\d/gmock\.sln"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googlemock/msvc/20\d\d/gmock.*vcproj"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googlemock/msvc/20\d\d/gmock.*vsprops"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googlemock/msvc/20\d\d/gmock.*vcxproj"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googlemock/msvc/20\d\d/gmock_config.props"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/codegear/gtest.*\.cbproj"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_all\.cc"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/codegear/gtest_link\.cc"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/codegear/gtest\.groupproj"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/msvc/gtest.*\.vcproj"
+extend-diff-ignore = ".*src/rapidjson/thirdparty/gtest/googletest/msvc/gtest.*\.sln"
diff --git a/tests/build-rados b/tests/build-rados
new file mode 100755 (executable)
index 0000000..c629992
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# autopkgtest check: Build and run a program against librados2 to 
+# validate that headers are installed and libraries exists
+
+set -e
+
+WORKDIR=$(mktemp -d)
+trap "rm -rf $WORKDIR" 0 INT QUIT ABRT PIPE TERM
+cd $WORKDIR
+cat <<EOF > radostest.c
+#include <rados/librados.h>
+
+int
+main(void)
+{
+    int err;
+    rados_t cluster;
+
+    err = rados_create(&cluster, NULL);
+    if (err < 0) {
+       return (1);
+    }
+    return(0);
+}
+EOF
+
+gcc -o radostest radostest.c -lrados
+echo "build: OK"
+[ -x radostest ]
+./radostest
+echo "run: OK"
diff --git a/tests/build-rbd b/tests/build-rbd
new file mode 100755 (executable)
index 0000000..5ad6f88
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+# autopkgtest check: Build and run a program against librbd1 to 
+# validate that headers are installed and libraries exists
+
+set -e
+
+WORKDIR=$(mktemp -d)
+trap "rm -rf $WORKDIR" 0 INT QUIT ABRT PIPE TERM
+cd $WORKDIR
+cat <<EOF > rbdtest.c
+#include <rbd/librbd.h>
+
+int
+main(void)
+{
+    return(0);
+}
+EOF
+
+gcc -o rbdtest rbdtest.c -lrbd
+echo "build: OK"
+[ -x rbdtest ]
+./rbdtest
+echo "run: OK"
diff --git a/tests/ceph-client b/tests/ceph-client
new file mode 100755 (executable)
index 0000000..c693a56
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+CLIENTS=('ceph')
+
+for client in "${CLIENTS[@]}"; do
+    echo -n "Testing client $client: "
+    $client -v 2>&1 > /dev/null
+    echo "OK"
+done
diff --git a/tests/control b/tests/control
new file mode 100644 (file)
index 0000000..372a057
--- /dev/null
@@ -0,0 +1,9 @@
+Tests: ceph-client build-rados build-rbd python-ceph
+Depends:
+ build-essential,
+ ceph-common,
+ librados-dev,
+ librbd-dev,
+ python3-rados,
+ python3-rbd,
+Restrictions: needs-root
diff --git a/tests/python-ceph b/tests/python-ceph
new file mode 100755 (executable)
index 0000000..006dbe6
--- /dev/null
@@ -0,0 +1,7 @@
+#!/usr/bin/python3
+
+# Test that rbd and rados can be imported OK
+import rbd
+import rados
+
+print("python-ceph: OK")
diff --git a/udev/95-ceph-osd-lvm.rules b/udev/95-ceph-osd-lvm.rules
new file mode 100644 (file)
index 0000000..00feae5
--- /dev/null
@@ -0,0 +1,13 @@
+# OSD LVM layout example
+# VG prefix: ceph-
+# LV prefix: osd-
+ACTION=="add", SUBSYSTEM=="block", \
+  ENV{DEVTYPE}=="disk", \
+  ENV{DM_LV_NAME}=="osd-*", \
+  ENV{DM_VG_NAME}=="ceph-*", \
+  OWNER:="ceph", GROUP:="ceph", MODE:="660"
+ACTION=="change", SUBSYSTEM=="block", \
+  ENV{DEVTYPE}=="disk", \
+  ENV{DM_LV_NAME}=="osd-*", \
+  ENV{DM_VG_NAME}=="ceph-*", \
+  OWNER="ceph", GROUP="ceph", MODE="660"
diff --git a/watch b/watch
new file mode 100644 (file)
index 0000000..279f3d5
--- /dev/null
+++ b/watch
@@ -0,0 +1,3 @@
+version=3
+opts="uversionmangle=s/-/~/,dversionmangle=s/\+dfsg\d*$//" \
+ http://download.ceph.com/tarballs/ceph-(\d.*)\.tar\.gz
diff --git a/workarounds/ceph-dencoder-oom b/workarounds/ceph-dencoder-oom
new file mode 100644 (file)
index 0000000..8720213
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+echo "Unfortunately it is not possible to compile ceph-dencoder" 1>&2
+echo "on this architecture, see bug #947886." 1>&2
+echo "" 1>&2
+exit 1
+