ceph (16.2.10+ds-2) unstable; urgency=medium
authorThomas Goirand <zigo@debian.org>
Sun, 31 Jul 2022 19:59:55 +0000 (20:59 +0100)
committerThomas Goirand <zigo@debian.org>
Sun, 31 Jul 2022 19:59:55 +0000 (20:59 +0100)
  * Replace Fix-build-with-fmt-8.1.1.patch by Fix-build-with-fmt-8-9.patch
    thanks to Shengjing Zhu <zhushengjing@cambricon.com> (Closes: #1014549).

[dgit import unpatched ceph 16.2.10+ds-2]

140 files changed:
1  2 
debian/.gitlab-ci.yml
debian/README.Debian
debian/calc-max-parallel.sh
debian/ceph-base.ceph.init
debian/ceph-base.dirs
debian/ceph-base.docs
debian/ceph-base.install
debian/ceph-base.postinst
debian/ceph-base.postrm
debian/ceph-common.dirs
debian/ceph-common.install
debian/ceph-common.lintian-overrides
debian/ceph-common.manpages
debian/ceph-common.postinst
debian/ceph-common.postrm
debian/ceph-common.preinst
debian/ceph-common.rbdmap.init
debian/ceph-fs-common.install
debian/ceph-fuse.install
debian/ceph-fuse.lintian-overrides
debian/ceph-fuse.manpages
debian/ceph-grafana-dashboards.install
debian/ceph-immutable-object-cache.install
debian/ceph-mds.dirs
debian/ceph-mds.install
debian/ceph-mds.lintian-overrides
debian/ceph-mds.postinst
debian/ceph-mgr-cephadm.install
debian/ceph-mgr-cephadm.postinst
debian/ceph-mgr-dashboard.install
debian/ceph-mgr-dashboard.postinst
debian/ceph-mgr-k8sevents.install
debian/ceph-mgr-k8sevents.postinst
debian/ceph-mgr-modules-core.install
debian/ceph-mgr-rook.install
debian/ceph-mgr-rook.postinst
debian/ceph-mgr.dirs
debian/ceph-mgr.install
debian/ceph-mgr.postinst
debian/ceph-mon.dirs
debian/ceph-mon.install
debian/ceph-mon.postinst
debian/ceph-osd.dirs
debian/ceph-osd.install
debian/ceph-prometheus-alerts.install
debian/ceph-resource-agents.install
debian/ceph-test.install
debian/ceph.NEWS
debian/ceph.lintian-overrides
debian/cephadm.install
debian/cephadm.postinst
debian/cephfs-mirror.install
debian/cephfs-shell.install
debian/cephfs-top.install
debian/changelog
debian/clean
debian/control
debian/copyright
debian/gbp.conf
debian/lib-systemd/system-sleep/ceph
debian/lib-systemd/system/ceph-create-keys.service
debian/lib-systemd/system/ceph-mds.service
debian/lib-systemd/system/ceph-mon.service
debian/lib-systemd/system/ceph-osd@.service
debian/libcephfs-dev.install
debian/libcephfs-java.jlibs
debian/libcephfs-jni.install
debian/libcephfs-jni.lintian-overrides
debian/libcephfs2.install
debian/libcephfs2.lintian-overrides
debian/libcephfs2.symbols
debian/librados-dev.install
debian/librados2.install
debian/librados2.lintian-overrides
debian/libradospp-dev.install
debian/libradosstriper-dev.install
debian/libradosstriper1.install
debian/libradosstriper1.symbols
debian/librbd-dev.install
debian/librbd1.install
debian/librbd1.symbols
debian/librgw-dev.install
debian/librgw2.install
debian/libsqlite3-mod-ceph-dev.install
debian/libsqlite3-mod-ceph.install
debian/man/ceph-crush-location.1
debian/man/mount.fuse.ceph.8
debian/missing-sources/bootstrap.js
debian/missing-sources/two.js
debian/patches/32bit-fixes.patch
debian/patches/Fix-build-with-fmt-8-9.patch
debian/patches/add-option-to-disable-ceph-dencoder.patch
debian/patches/bug1914584.patch
debian/patches/bug1917414.patch
debian/patches/civetweb-755-1.8-somaxconn-configurable.patch
debian/patches/civetweb-755-1.8-somaxconn-configurable_conf.patch
debian/patches/civetweb-755-1.8-somaxconn-configurable_test.patch
debian/patches/cmake-test-for-16-bytes-atomic-support-on-mips-also.patch
debian/patches/compile-ppc.c-on-all-powerpc-machines.patch
debian/patches/debian-armel-armhf-buildflags.patch
debian/patches/disable-crypto.patch
debian/patches/enable-strsignal.patch
debian/patches/fix-FTBFS-include-memory.h.patch
debian/patches/fix-bash-completion-location
debian/patches/fix-ceph-osd-systemd-target.patch
debian/patches/only-yied-under-armv7-and-above.patch
debian/patches/riscv64-link-pthread.patch
debian/patches/series
debian/patches/update-java-source-target-flags.patch
debian/py3dist-overrides
debian/python3-ceph-argparse.install
debian/python3-ceph-common.install
debian/python3-ceph.lintian-overrides
debian/python3-cephfs.install
debian/python3-rados.install
debian/python3-rbd.install
debian/python3-rgw.install
debian/rados-objclass-dev.install
debian/radosgw.dirs
debian/radosgw.install
debian/radosgw.lintian-overrides
debian/radosgw.postinst
debian/radosgw.prerm
debian/rbd-fuse.install
debian/rbd-mirror.install
debian/rbd-nbd.install
debian/rest-bench.install
debian/rules
debian/source.lintian-overrides
debian/source/format
debian/source/lintian-overrides
debian/source/options
debian/tests/build-rados
debian/tests/build-rbd
debian/tests/ceph-client
debian/tests/control
debian/tests/python-ceph
debian/udev/95-ceph-osd-lvm.rules
debian/watch
debian/workarounds/ceph-dencoder-oom

index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9815b7cd1412c8e90403a0ae48301b34dab79702
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..be21ad791cffaee761b5992d6c8b7ba774f6da66
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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"
++
++-------- -------- --------
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d55d966c748bf4687c2cda7e9dc30188f5db440e
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,37 @@@
++#!/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
++
++if [ ""$(dpkg-architecture -qDEB_HOST_ARCH_BITS) = 32 ] ; then
++      echo "--max-parallel=1"
++      exit 0
++fi
++
++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=8"
++elif [ ${total_ram} -le ${fourtyheight_g} ]; then
++    echo "--max-parallel=14"
++elif [ ${total_ram} -le ${sixtyfour_g} ]; then
++    echo "--max-parallel=18"
++else
++    echo "--max-parallel=24"
++fi
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b538109d2e09599f2dba1120f90176ba415493d3
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++../src/init-ceph
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cf605ad444616c434cf55401f925eeecf6286cf7
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b43bf86b50fd8d3529a0dc062c30006ed38f309e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++README.md
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..704e7c55d02a0b38c6a6016415dea2927e200a29
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,20 @@@
++## install from source tree
++etc/sudoers.d/ceph-smartctl
++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-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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..23712a8b4e49262d522c0df0e96013d1d5c8c124
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..05091b223542ac0f8dc027047975fb4183db2d3b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,13 @@@
++#!/bin/sh
++
++set -e
++
++if [ "${1}" = "purge" ] ; then
++      rm -rf /var/log/ceph 
++fi
++
++#DEBHELPER#
++
++exit 0
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ff05698c236975ae8f1498f898dd09a3b721feeb
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++etc/ceph
++var/lib/ceph
++var/log/ceph
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9f6d385c2c03c31cc290290ed421bec7641ecf53
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d4041ca8e38f6d6b87374fdde45dd17d17748cba
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++# False-positives:
++spelling-error-in-binary * tEH the
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..643fa2a415a4695c934616b909707a7169074fab
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++debian/man/ceph-crush-location.1
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..32e58fe06fc8a0e3ee63d60030a0ec2e57918b19
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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 $(command -v 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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e4051180d915212441d8619de28eb2e1c1d26b26
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ef14f1eb1a044742e8808c9643ee1d1144ca563b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b2de0ce8b029ff5db726be822f15a327e62a7f67
new file mode 120000 (symlink)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++../src/init-rbdmap
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a4f0bab50c01ede2cb0ede657db241543946b09c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4 @@@
++usr/bin/cephfs
++usr/sbin/mount.ceph sbin
++usr/share/man/man8/cephfs.8
++usr/share/man/man8/mount.ceph.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..565ef25dda4c60fd530d5bba9dc9c3f7f58a3f12
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,5 @@@
++lib/systemd/system/ceph-fuse*
++usr/bin/ceph-fuse
++usr/sbin/mount.fuse.ceph sbin
++usr/share/man/man8/ceph-fuse.8
++usr/share/man/man8/mount.fuse.ceph.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d4041ca8e38f6d6b87374fdde45dd17d17748cba
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++# False-positives:
++spelling-error-in-binary * tEH the
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e4c9c231e0c10951722478cd723064bc5aaecadd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++debian/man/mount.fuse.ceph.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6639e569562d6d65932dcc33464e0505ae5c062c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++etc/grafana/dashboards/ceph-dashboard/*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..76daf1928c3ac258c4d81718eb994f2f4511a762
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++lib/systemd/system/ceph-immutable-object-cache*
++usr/bin/ceph-immutable-object-cache
++usr/share/man/man8/ceph-immutable-object-cache.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9845268080de8d3acfa594c74af101e2296ef9de
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++var/lib/ceph/mds
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a26d5596c59a0c117bba2ad4697ede487c8507d0
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++lib/systemd/system/ceph-mds*
++usr/bin/ceph-mds
++usr/share/man/man8/ceph-mds.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c8839544161b024dfe90cf4cbca15ec8bb8e3b79
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ba91b17cd540e9dd1bad86736be4d0fce0f517d5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8dcbda16540e268c2b44b4786c271ce731bdeb9c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/share/ceph/mgr/cephadm
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..18731dd072e51dddf6b10188c6cbb45ac7e3cb78
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,11 @@@
++#!/bin/sh
++
++set -e
++
++if [ "${1}" = "configure" ] || [ "$1" = "reconfigure" ] ; then
++      deb-systemd-invoke try-restart ceph-mgr.target
++fi
++
++#DEBHELPER#
++
++exit 0
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8d3c8bd17fc10a41fc41e24a08c6753cb2d4d226
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/share/ceph/mgr/dashboard
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e681ef6b62f7c654b337941204ee6642d3915b7a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..734da94ca2d1b4b1f238bf48e4a67ad3d9046553
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/share/ceph/mgr/k8sevents
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4ab578fd1afc7a0121a42e59adf1205d152d04f2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e99f78efb9fc1d2d166c7085007ca40f79a81e71
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,27 @@@
++usr/share/ceph/mgr/alerts
++usr/share/ceph/mgr/balancer
++usr/share/ceph/mgr/crash
++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/mirroring
++usr/share/ceph/mgr/nfs
++usr/share/ceph/mgr/orchestrator
++usr/share/ceph/mgr/osd_perf_query
++usr/share/ceph/mgr/osd_support
++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/snap_schedule
++usr/share/ceph/mgr/stats
++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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..50cadb435d367930c983067de33a20e51b4e92d9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/share/ceph/mgr/rook
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a3293a88e2f4c9b9bc706e26c1d4f4f4554871e1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..636b3cf4a12a3064d451fefa00abfdceb67fa40d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++var/lib/ceph/mgr
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fd7b68e82c746dbc33e710843ccae6c128cc81d3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4 @@@
++lib/systemd/system/ceph-mgr*
++usr/bin/ceph-mgr
++usr/share/ceph/mgr/mgr_module.*
++usr/share/ceph/mgr/mgr_util.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6d38ccf09feb3ea3cc9c6255320485a61207be94
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e2845f60229d0111626790dcb1f790f77831fe4a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++var/lib/ceph/mon
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ff429f17a6af4fefbc5d716c946890ce39812295
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4 @@@
++lib/systemd/system/ceph-mon*
++usr/bin/ceph-mon
++usr/bin/ceph-monstore-tool
++usr/share/man/man8/ceph-mon.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..454ffc141450fc9ff1b68fef0b55b8569084e347
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b7fc476185f7372cd042b7753dea66272ac45e73
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++var/lib/ceph/osd
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a5950649b95241172741950383449644545106c1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,20 @@@
++debian/udev/* lib/udev/rules.d
++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-erasure-code-tool
++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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..13b6fc46b7625c1c305bb43e4d5ddd4fd833e10a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++etc/prometheus/ceph/ceph_default_alerts.yml
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..30843f62ffebd49cff2d3582336e11add656e91a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/ocf/resource.d/ceph/*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..978d992bce50b89af9c2d522733171a5316db114
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/share/java/libcephfs-test.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ee9db2f2237950463f9026f029e44ad4db5effe8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..99b3db84b6c93d439718cc00c68f99e3d05c4d7e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++empty-binary-package
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f30ed9c5f5ad0904f0b3a1cf3ea0712854f4b17c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/sbin/cephadm
++usr/share/man/man8/cephadm.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2ff4cf46080a5965e21da981319f7e1b85cb104e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,46 @@@
++#!/bin/sh
++
++set -e
++
++CEPHADM_HOME=/var/lib/cephadm
++
++if [ "${1}" = "configure" ] || [ "${1}" = "reconfigure" ] ; then
++      if ! getent group cephadm > /dev/null 2>&1 ; then
++              addgroup --quiet --system cephadm ${ADDGROUP_PARAM}
++      fi
++
++      if ! getent passwd cephadm ; then
++              echo -n "Adding system user cephadm..."
++              adduser --system \
++                      --home /var/lib/cephadm \
++                      --no-create-home \
++                      --quiet \
++                      --disabled-password \
++                      --gecos 'cephadm user for mgr/cephadm' \
++                      --shell /bin/bash \
++                      --ingroup cephadm \
++                      cephadm 2>/dev/null || true
++              echo "done"
++      fi
++
++      if [ ! -d ${CEPHADM_HOME} ] ; then
++              mkdir -p ${CEPHADM_HOME}
++      fi
++      chown cephadm:cephadm ${CEPHADM_HOME}
++      chmod 0755 ${CEPHADM_HOME}
++
++      if ! [ -d ${CEPHADM_HOME}/.ssh ] ; then
++              mkdir ${CEPHADM_HOME}/.ssh
++              chown --reference ${CEPHADM_HOME} ${CEPHADM_HOME}/.ssh
++              chmod 0700 ${CEPHADM_HOME}/.ssh
++      fi
++      if ! [ -e ${CEPHADM_HOME}/.ssh/authorized_keys ] ; then
++              touch ${CEPHADM_HOME}/.ssh/authorized_keys
++              chown --reference ${CEPHADM_HOME} ${CEPHADM_HOME}/.ssh/authorized_keys
++              chmod 0600 ${CEPHADM_HOME}/.ssh/authorized_keys
++      fi
++fi
++
++#DEBHELPER#
++
++exit 0
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..19d2e483d9586d15a67bd10cf16f4537a513e968
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/bin/cephfs-mirror
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4713a81b11578d852c2a4378cdeebcf180d3041d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/bin/cephfs-shell
++usr/lib/python3*/dist-packages/cephfs_shell-*.egg-info
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..930396b0c68f3b833ac3c57c88db8dc10d5d5fa9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/bin/cephfs-top
++usr/lib/python3*/dist-packages/cephfs_top-*.egg-info
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..84c571724e241fab69eb994307ae80380e08e0d5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1704 @@@
++ceph (16.2.10+ds-2) unstable; urgency=medium
++
++  * Replace Fix-build-with-fmt-8.1.1.patch by Fix-build-with-fmt-8-9.patch
++    thanks to Shengjing Zhu <zhushengjing@cambricon.com> (Closes: #1014549).
++
++ -- Thomas Goirand <zigo@debian.org>  Sun, 31 Jul 2022 21:59:55 +0200
++
++ceph (16.2.10+ds-1) unstable; urgency=high
++
++  * New upstream hotfix release:
++    - CVE-2022-0670: Users who were running OpenStack Manila to export native
++      CephFS, who upgraded their Ceph cluster from Nautilus (or earlier) to a
++      later major version, were vulnerable to an attack by malicious users
++      (Closes: #1016069).
++    - A regression made it possible to dereference a null pointer for for
++      s3website requests that don’t refer to a bucket resulting in an RGW
++      segfault.
++  * Add fix-FTBFS-include-memory.h.patch to address FTBFS under GCC 12.
++
++ -- Thomas Goirand <zigo@debian.org>  Wed, 27 Jul 2022 09:30:47 +0200
++
++ceph (16.2.9+ds-1) unstable; urgency=medium
++
++  * New upstream release.
++  * Fix patches:
++    - refreshed enable-strsignal.patch.
++    - removed disable-log-slow-requests.patch.
++  * Fix prometheus_alerts.yml path in d/rules.
++
++ -- Thomas Goirand <zigo@debian.org>  Fri, 15 Jul 2022 17:27:25 +0200
++
++ceph (16.2.7+ds-5) unstable; urgency=medium
++
++  * Fix cephfs-top package long and short description (Closes: #1004246).
++  * Drop the ceph-mgr-diskprediction-local package, because it relies on the
++    python3-sklearn package, which the release team is dropping. Also, it
++    relies on pre-trained data, which aren't built from source, so it is
++    considered non-free in Debian (Closes: #1014641).
++
++ -- Thomas Goirand <zigo@debian.org>  Fri, 15 Jul 2022 11:31:04 +0200
++
++ceph (16.2.7+ds-4) unstable; urgency=medium
++
++  * Add fix-build-with-fmt-8.1.1.patch (Closes: #1003469).
++
++ -- Thomas Goirand <zigo@debian.org>  Fri, 14 Jan 2022 10:50:50 +0100
++
++ceph (16.2.7+ds-3) unstable; urgency=medium
++
++  * Rebuild source-only.
++  * Fix cephadm user creation that was failing, and subsequent chown failing as
++    the package installation as a consequence.
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 10 Jan 2022 11:45:19 +0100
++
++ceph (16.2.7+ds-2) unstable; urgency=medium
++
++  * Add libgf-complete-dev as build-dependency, since the Ceph repository
++    contains the exact same code as the Debian packaged version.
++  * Add libjerasure-dev as build-dependency, as the code of the Debian package
++    only contains a little bit further input checking and nothing else, so the
++    Ceph embedded copy is just less good (a simple diff shows this). This also
++    closes a very long silly debate that could have been solved by simply
++    reading the diff (Closes: #791445).
++  * libsqlite3-mod-ceph-dbg: do not depend on libsqlite3-0-dbgsym.
++  * Add missing python3-ceph-common package (Closes: #1003013).
++  * Set --max-parallel=8 when 32 GB (this optimizes build on my laptop).
++  * Kill the ceph-fuse-dbg package that is uninstallable.
++  * Remove ceph-base depends on essential packages: debianutils, findutils.
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 03 Jan 2022 15:24:32 +0100
++
++ceph (16.2.7+ds-1) unstable; urgency=medium
++
++  * New upstream release:
++    - Fix data corruption on upgrades from older releases (Closes: #1002749).
++  * Remove build-dependency on clang: we use gcc on all arch.
++  * Move smartmontools and nvme-cli to ceph-base, as per upstream.
++  * Increase the number of parallel processes when building with a lot of RAM.
++  * Refreshed a few patches.
++
++ -- Thomas Goirand <zigo@debian.org>  Wed, 29 Dec 2021 09:29:49 +0100
++
++ceph (16.2.6+ds-11) unstable; urgency=medium
++
++  * Revert libcephfs2.symbols "fix".
++
++ -- Thomas Goirand <zigo@debian.org>  Tue, 28 Dec 2021 08:45:18 +0100
++
++ceph (16.2.6+ds-10) unstable; urgency=medium
++
++  * Fix libcephfs2.symbols.
++  * Build-depends only on python3-dev, not python3-all-dev (Closes: #1002688).
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 27 Dec 2021 15:48:02 +0100
++
++ceph (16.2.6+ds-9) unstable; urgency=medium
++
++  * Fix ceph-mgr-modules-core,ceph-mgr: both ship /usr/share/ceph/mgr/*
++    (Closes: #998826).
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 27 Dec 2021 10:16:27 +0100
++
++ceph (16.2.6+ds-8) unstable; urgency=medium
++
++  * Uploading to unstable.
++
++ -- Thomas Goirand <zigo@debian.org>  Fri, 24 Dec 2021 23:46:33 +0100
++
++ceph (16.2.6+ds-7) experimental; urgency=medium
++
++    [ Adrian Bunk ]
++    * Use the system libfmt
++    * Use the system liburing
++    * Optimize for space on 32bit architectures
++    * Don't let cmake append CFLAGS/CXXFLAGS is -O/-g settings different from
++      what we set.
++
++ -- Thomas Goirand <zigo@debian.org>  Thu, 23 Dec 2021 15:53:49 +0100
++
++ceph (16.2.6+ds-6) experimental; urgency=medium
++
++  * Use -DWITH_BOOST_CONTEXT=ON in s390x mips64el and riscv64.
++  * Fix cmake-test-for-16-bytes-atomic-support-on-mips-also.patch so that it
++    works also for 32 bits mips.
++
++ -- Thomas Goirand <zigo@debian.org>  Thu, 09 Dec 2021 13:54:42 +0100
++
++ceph (16.2.6+ds-5) experimental; urgency=medium
++
++  * Add cmake-test-for-16-bytes-atomic-support-on-mips-also.patch which fixes
++    FTBFS on mips64el.
++  * Add only-yied-under-armv7-and-above.patch which fixes FTBFS on armel.
++
++ -- Thomas Goirand <zigo@debian.org>  Sat, 20 Nov 2021 15:00:51 +0100
++
++ceph (16.2.6+ds-4) experimental; urgency=medium
++
++  * Add libgoogle-perftools-dev build-depends on all available arch.
++  * Remove a symbol in debian/libcephfs2.symbols that is missing on ppc64el.
++  * Removed useless libsqlite3-mod-ceph.symbols.
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 08 Nov 2021 20:44:04 +0100
++
++ceph (16.2.6+ds-3) experimental; urgency=medium
++
++  * Do not maintain debian/librados2.symbols.
++
++ -- Thomas Goirand <zigo@debian.org>  Mon, 08 Nov 2021 08:36:08 +0100
++
++ceph (16.2.6+ds-2) experimental; urgency=medium
++
++  * Build-depends on valgrind for all avialable arch.
++  * Build-depends on libboost-context-dev and libboost-coroutine-dev on
++    s390x, mips64el and riscv64.
++
++ -- Thomas Goirand <zigo@debian.org>  Sun, 07 Nov 2021 15:14:08 +0100
++
++ceph (16.2.6+ds-1) experimental; urgency=medium
++
++  * New upstream release.
++  * Exclude debian folder from upstream orig tarball.
++  * Commit some of the upstream packaging to be closer to their packaging.
++  * Refreshed patch:
++    - disable-crypto.patch
++    - riscv64-link-pthread.patch
++    - compile-ppc.c-on-all-powerpc-machines.patch
++  * Remove patch (now useless / NA):
++    - 32bit-avoid-overloading.patch
++    - 32bit-avoid-size_t.patch
++    - make-ceph-python-3.9-aware.patch
++    - patches/another-cmakelists-fix.patch
++    - cmake_add_1.74_to_known_versions.patch
++    - cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost...sers.patch
++    - allow-bgp-to-host.patch
++  * Add patch taken from Ubuntu:
++    - enable-strsignal.patch
++    - 32bit-fixes.patch
++    - disable-log-slow-requests.patch
++    - bug1914584.patch
++    - bug1917414.patch
++  * Removed patch applied upstream:
++    - bluefs-use-uint64_t-for-len.patch
++    - debian/patches/mds-purgequeue-use_uint64_t.patch
++  * Rebased patch:
++    - add-option-to-disable-ceph-dencoder.patch
++  * d/rules: import stuff from the Ubuntu packaging.
++  * Reviewed build-dependency list.
++  * Switch to debhelper-compat 11.
++  * Use dh_installsystemd, not dh_systemd_enable which is deprecated.
++  * Reviewed (and rewrote a lot of) debian/copyright.
++  * Do not override dh_systemd_enable and dh_systemd_start.
++
++ -- Thomas Goirand <zigo@debian.org>  Tue, 02 Nov 2021 14:48:40 +0100
++
++ceph (14.2.21-1.1) unstable; urgency=medium
++
++  * Non-maintainer upload.
++  * Compile ppc.c on all powerpc machines. Fixes 32 bit powerpc build
++    (Closes: #993642).
++
++ -- Mattias Ellert <mattias.ellert@physics.uu.se>  Thu, 26 Aug 2021 10:36:49 +0200
++
++ceph (14.2.21-1) unstable; urgency=high
++
++  * New upstream release, resolving these:
++    - CVE-2021-3509: Cross Site Scripting via token Cookie (Closes: #988888).
++    - CVE-2021-3524: injection of HTTP headers via a CORS ExposeHeader tag in
++      the Ceph Storage RadosGW (Closes: #988889).
++    - CVE-2021-3531: RadosGW denial of service (crash) (Closes: #988890).
++
++ -- Thomas Goirand <zigo@debian.org>  Thu, 27 May 2021 12:04:21 +0200
++
++ceph (14.2.20-2) unstable; urgency=medium
++
++  * Add allow-bgp-to-host.patch.
++
++ -- Thomas Goirand <zigo@debian.org>  Wed, 21 Apr 2021 10:02:07 +0200
++
++ceph (14.2.20-1) unstable; urgency=medium
++
++  * New upstream point release (Closes: #986173):
++    - Fixes CVE-2021-20288: Unauthorized global_id reuse in cephx
++      (Closes: #986974)
++  * Remove "rm -rf /etc/ceph" in ceph-base.postinst (Closes: #987192).
++
++ -- Thomas Goirand <zigo@debian.org>  Tue, 20 Apr 2021 13:00:13 +0200
++
++ceph (14.2.18-1) unstable; urgency=medium
++
++  * New upstream point release.
++  * Refreshed 32bit-avoid-size_t.patch.
++  * Rebased bluefs-use-uint64_t-for-len.patch.
++  * Refreshed 32bit-avoid-size_t.patch.
++  * Refreshed riscv64-link-pthread.patch.
++  * Rebased
++  cmake_define_BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT_for_Boost.Asio_users.patch
++  * Refreshed another-cmakelists-fix.patch.
++  * Removed allow-binding-on-lo.patch applied upstream.
++
++ -- Thomas Goirand <zigo@debian.org>  Wed, 24 Mar 2021 12:49:20 +0100
++
++ceph (14.2.16-2) unstable; urgency=medium
++
++  * Add fix-ceph-osd-systemd-target.patch.
++
++ -- Thomas Goirand <zigo@debian.org>  Thu, 28 Jan 2021 16:45:23 +0100
++
++ceph (14.2.16-1) unstable; urgency=medium
++
++  * Add allow-binding-on-lo.patch.
++  * New upstream release.
++
++ -- Thomas Goirand <zigo@debian.org>  Fri, 15 Jan 2021 12:26:14 +0100
++
++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 --cc debian/clean
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8638ac91afab5950bbd43f1fc686235b117bebb9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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 --cc debian/control
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ec1007ec6439155f0543c93b9e0611a4e437d7d9
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1194 @@@
++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-compat (= 11),
++ 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.74.0),
++ libboost-chrono-dev (>= 1.74.0),
++ libboost-context-dev (>= 1.74.0) [!ia64 !m68k !ppc64 !sh4 !sparc64 !x32 !alpha],
++ libboost-coroutine-dev (>= 1.74.0) [!ia64 !m68k !ppc64 !sh4 !sparc64 !x32 !alpha],
++ libboost-date-time-dev (>= 1.74.0),
++ libboost-filesystem-dev (>= 1.74.0),
++ libboost-iostreams-dev (>= 1.74.0),
++ libboost-program-options-dev (>= 1.74.0),
++ libboost-python-dev (>= 1.74.0),
++ libboost-random-dev (>= 1.74.0),
++ libboost-regex-dev (>= 1.74.0),
++ libboost-system-dev (>= 1.74.0),
++ libboost-test-dev (>= 1.74.0),
++ libboost-thread-dev (>= 1.74.0),
++ libboost-timer-dev (>= 1.74.0),
++ libbz2-dev,
++ libc-ares-dev,
++ libcap-ng-dev,
++ libcrypto++-dev,
++ libcryptsetup-dev,
++ libcunit1-dev,
++ libcurl4-gnutls-dev,
++ libedit-dev,
++ libexpat1-dev,
++ libfmt-dev,
++ libfuse-dev,
++ libgf-complete-dev,
++ libgnutls28-dev,
++ libgoogle-perftools-dev [amd64 arm64 armel armhf i386 mips64el mipsel ppc64 ppc64el riscv64 powerpc s390x],
++ libhwloc-dev,
++ libibverbs-dev,
++ libicu-dev,
++ libjerasure-dev,
++ libkeyutils-dev,
++ libldap2-dev,
++ libleveldb-dev,
++ liblua5.3-dev,
++ liblz4-dev (>= 0.0~r131),
++ libncurses-dev,
++ libnl-3-dev,
++ libnl-genl-3-dev,
++ libnss3-dev,
++ libnuma-dev,
++ liboath-dev,
++ libpciaccess-dev,
++ libpmem-dev [amd64 arm64 ppc64el],
++ libpmemobj-dev [amd64 arm64 ppc64el],
++ libprotobuf-dev,
++ librabbitmq-dev,
++ librdmacm-dev,
++ libsctp-dev,
++ libsnappy-dev,
++ libsqlite3-dev,
++ libssl-dev,
++ libtool,
++ libudev-dev,
++ liburing-dev,
++ libxml2-dev,
++ libyaml-cpp-dev,
++ lsb-release,
++ nasm [amd64],
++ pkg-config,
++ protobuf-compiler,
++ python3-cherrypy3,
++ python3-dev,
++ python3-pecan,
++ python3-setuptools,
++ python3-sphinx,
++ ragel,
++ systemtap-sdt-dev,
++ tox,
++ uuid-runtime,
++ valgrind [amd64 armhf arm64 i386 mips64el mipsel ppc64 ppc64el s390x],
++ virtualenv,
++ xfslibs-dev,
++ zlib1g-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,
++ e2fsprogs,
++ gdisk,
++ hdparm | sdparm,
++ logrotate,
++ nvme-cli,
++ parted,
++ psmisc,
++ smartmontools,
++ uuid-runtime,
++ xfsprogs,
++ ${misc:Depends},
++ ${python3:Depends},
++ ${shlibs:Depends},
++Pre-Depends:
++ ${misc:Pre-Depends},
++Recommends:
++ btrfs-tools,
++ ceph-mds (= ${binary:Version}),
++ chrony | time-daemon | ntp,
++ librados2 (= ${binary:Version}),
++ libradosstriper1 (= ${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-base-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-base (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-base
++ Ceph is a distributed storage system designed to provide excellent
++ performance, reliability, and scalability.
++ .
++ This package contains the debugging symbols for ceph-base.
++
++Package: ceph-common
++Architecture: linux-any
++Depends:
++ librbd1 (= ${binary:Version}),
++ python3-ceph-argparse (= ${binary:Version}),
++ python3-ceph-common (<< ${source:Version}.1~),
++ python3-ceph-common (>= ${source: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,
++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-common-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-common (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-common
++ Ceph is a distributed storage system designed to provide excellent
++ performance, reliability, and scalability.
++ .
++ This package contains the debugging symbols for ceph-common.
++
++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-grafana-dashboards
++Architecture: all
++Depends:
++ ${misc:Depends},
++Description: grafana dashboards for the ceph dashboard
++ 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 Grafana dashboards that are used by the Ceph Dashboard
++ for monitoring.
++
++Package: ceph-immutable-object-cache
++Architecture: linux-any
++Depends:
++ ceph-common (= ${binary:Version}),
++ librados2 (= ${binary:Version}),
++ ${misc:Depends},
++ ${shlibs:Depends},
++Description: Ceph daemon for immutable object cache
++ 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 daemon for immutable
++ object cache.
++
++Package: ceph-immutable-object-cache-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-immutable-object-cache (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-immutable-object-cache
++ 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 daemon for immutable
++ object cache.
++ .
++ This package contains the debugging symbols for ceph-immutable-object-cache.
++
++Package: ceph-mds
++Architecture: linux-any
++Depends:
++ ceph,
++ ceph-base (= ${binary:Version}),
++ ${misc:Depends},
++ ${shlibs:Depends},
++Recommends:
++ ceph-common,
++ ceph-fuse,
++ libcephfs2,
++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-mds-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-mds (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-mds
++ 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 debugging symbols for ceph-mds.
++
++Package: ceph-mgr
++Architecture: linux-any
++Depends:
++ ceph-base (= ${binary:Version}),
++ ceph-mgr-modules-core (<< ${source:Version}.1~),
++ ceph-mgr-modules-core (>= ${source:Version}),
++ python3-bcrypt,
++ python3-cherrypy3,
++ python3-jwt,
++ python3-openssl,
++ python3-pecan,
++ python3-requests,
++ python3-werkzeug,
++ ${misc:Depends},
++ ${python3:Depends},
++ ${shlibs:Depends},
++Suggests:
++ ceph-mgr-dashboard,
++ ceph-mgr-diskprediction-local,
++ ceph-mgr-rook,
++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-cephadm
++Architecture: all
++Depends:
++ ceph-mgr (<< ${source:Version}.1~),
++ ceph-mgr (>= ${source:Version}),
++ cephadm,
++ openssh-client,
++ python3-jinja2,
++ ${misc:Depends},
++ ${python3:Depends},
++Description: cephadm orchestrator module 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 CEPHADM module for ceph-mgr's orchestration
++ functionality, to allow ceph-mgr to perform orchestration functions
++ over a standard SSH connection.
++
++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-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-mgr (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols 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 debugging symbols for ceph-mgr.
++
++Package: ceph-mgr-k8sevents
++Architecture: all
++Depends:
++ ceph-mgr (>= ${binary:Version}),
++ python3-kubernetes,
++ ${misc:Depends},
++ ${python3: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-modules-core
++Architecture: all
++Depends:
++ python3-dateutil,
++ python3-openssl,
++ ${misc:Depends},
++ ${python3:Depends},
++ ${shlibs:Depends},
++Replaces:
++ ceph-mgr (<< 15.1.0),
++Breaks:
++ ceph-mgr (<< 15.1.0),
++Description: ceph manager modules which are always enabled
++ 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 a set of core ceph-mgr modules which are always
++ enabled.
++
++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-mon
++Architecture: linux-any
++Depends:
++ ceph-base (= ${binary:Version}),
++ ${misc:Depends},
++ ${python3:Depends},
++ ${shlibs:Depends},
++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-mon-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-mon (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-mon
++ 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 debugging symbols for ceph-mon.
++
++Package: ceph-osd
++Architecture: linux-any
++Depends:
++ ceph-base (= ${binary:Version}),
++ lvm2,
++ sudo,
++ ${misc:Depends},
++ ${python3:Depends},
++ ${shlibs:Depends},
++Suggests:
++ nvme-cli,
++Pre-Depends:
++ ceph-common (= ${binary:Version}),
++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-osd-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ ceph-osd (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for ceph-osd
++ 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 debugging symbols for ceph-osd.
++
++Package: ceph-prometheus-alerts
++Architecture: all
++Depends:
++ ${misc:Depends},
++Description: prometheus alerts for the ceph dashboard
++ 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 alerts used for prometheus to interact with the
++ Ceph Dashboard.
++
++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: ceph-test
++Architecture: linux-any
++Depends:
++ ceph-common (= ${binary:Version}),
++ curl,
++ jq,
++ socat,
++ xmlstarlet,
++ ${misc:Depends},
++ ${shlibs:Depends},
++Description: Ceph test and benchmarking tools
++ This package contains tools for testing and benchmarking Ceph.
++
++Package: cephadm
++Architecture: linux-any
++Recommends:
++ podman (>= 2.0.2) | docker.io,
++Depends:
++ lvm2,
++ ${misc:Depends},
++ ${python3:Depends},
++Description: utility to bootstrap ceph daemons with systemd and containers
++ Ceph is a massively scalable, open-source, distributed
++ storage system that runs on commodity hardware and delivers object,
++ block and file system storage.
++ .
++ The cephadm utility is used to bootstrap a Ceph cluster and to manage
++ ceph daemons deployed with systemd and containers.
++
++Package: cephfs-mirror
++Architecture: linux-any
++Depends:
++ ceph-common (= ${binary:Version}),
++ libcephfs2 (= ${binary:Version}),
++ librados2 (= ${binary:Version}),
++ ${misc:Depends},
++ ${shlibs:Depends},
++Description: 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 daemon for mirroring CephFS
++ directory snapshots between Ceph clusters.
++
++Package: cephfs-mirror-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ cephfs-mirror (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for cephfs-mirror
++ 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  daemon for mirroring CephFS
++ directory snapshots between Ceph clusters.
++
++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: cephfs-top
++Architecture: all
++Depends:
++ ${misc:Depends},
++ ${python3:Depends},
++Description: top like utility for Ceph filesystem
++ 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 top(1) like utility to display various
++ filesystem metrics in realtime.
++ .
++ This package contains utility for displaying filesystem metrics.
++
++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: libcephfs2-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ libcephfs2 (= ${binary:Version}),
++ ${misc:Depends},
++Conflicts:
++ libceph1-dbg,
++Replaces:
++ libceph1-dbg,
++Description: debugging symbols for libcephfs2
++ 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
++ shared library allowing applications to access a Ceph distributed
++ file system via a POSIX-like interface.
++ .
++ This package contains debugging symbols for libcephfs2.
++
++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: librados2-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ librados2 (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for librados
++ 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 debugging symbols for librados.
++
++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: libradosstriper1-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ libradosstriper1 (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for libradosstriper
++ 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 debugging symbols for libradosstriper.
++
++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: librbd1-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ librbd1 (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for librbd1
++ 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 debugging symbols for librbd1.
++
++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: librgw2-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ librgw2 (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for librbd1
++ 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 debugging symbols for librgw2.
++
++Package: libsqlite3-mod-ceph
++Architecture: any
++Section: libs
++Depends:
++ ${misc:Depends},
++ ${shlibs:Depends},
++Description: SQLite3 VFS for Ceph
++ A SQLite3 VFS for storing and manipulating databases stored on Ceph's RADOS
++ distributed object store.
++ .
++ This packages contains the loadable extension module for SQLite3.
++
++Package: libsqlite3-mod-ceph-dbg
++Architecture: any
++Section: debug
++Depends:
++ libsqlite3-mod-ceph (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for libsqlite3-mod-ceph
++ A SQLite3 VFS for storing and manipulating databases stored on Ceph's RADOS
++ distributed object store.
++ .
++ This package contains debugging symbols for libsqlite3-mod-ceph.
++
++Package: libsqlite3-mod-ceph-dev
++Architecture: any
++Section: libdevel
++Depends:
++ libsqlite3-dev,
++ libsqlite3-mod-ceph (= ${binary:Version}),
++ ${misc:Depends},
++Description: SQLite3 VFS for Ceph (development files)
++ A SQLite3 VFS for storing and manipulating databases stored on Ceph's RADOS
++ distributed object store.
++ .
++ This package contains development files needed for building applications that
++ link against libsqlite3-mod-ceph.
++
++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},
++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-ceph-common
++Architecture: all
++Section: python
++Depends:
++ ${misc:Depends},
++ ${python3:Depends},
++Description: Python 3 utility libraries for Ceph
++ 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 data structures, classes and functions used by Ceph.
++ It also contains utilities used for the cephadm orchestrator.
++
++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.
++ 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 development files needed for building RADOS object class
++ plugins.
++
++Package: radosgw
++Architecture: linux-any
++Depends:
++ ceph-common (= ${binary:Version}),
++ librgw2 (= ${binary:Version}),
++ mime-support,
++ ${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: radosgw-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ radosgw (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for radosgw
++ 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 debugging symbols for radosgw.
++
++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-fuse-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ rbd-fuse (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for rbd-fuse
++ 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
++ FUSE-based client that allows one to map Ceph rbd images as files.
++ .
++ This package contains the debugging symbols for rbd-fuse.
++
++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-mirror-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ rbd-mirror (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for rbd-mirror
++ 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
++ daemon for mirroring RBD images between Ceph clusters, streaming
++ changes asynchronously.
++ .
++ This package contains the debugging symbols for rbd-mirror.
++
++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.
++
++Package: rbd-nbd-dbg
++Architecture: linux-any
++Section: debug
++Depends:
++ rbd-nbd (= ${binary:Version}),
++ ${misc:Depends},
++Description: debugging symbols for rbd-nbd
++ 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.
++ .
++ This package contains the debugging symbols for rbd-nbd.
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..33b9b850d392f82f0b044768c203aad772267f12
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1194 @@@
++Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
++Name: ceph
++Maintainer: Sage Weil <sage@newdream.net>
++Source: http://ceph.com/
++Files-Excluded: debian
++
++Files: *
++Copyright: 2004-2014 Sage Weil <sage@newdream.net>
++           2004-2014 Inktank <info@inktank.com>
++                     Inktank, Inc
++                     Inktank Storage, Inc.
++           2012-2021 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>
++           2004-2021, The Ceph Project Developers
++           2008-2011, New Dream Network
++           2019-2021, SUSE LINUX GmbH
++           2004-2006, Intel Corporation
++           2011-2014, Stanford University
++           2017 OVH
++           2016       Allen Samuels <allen.samuels@sandisk.com>
++           2014       UnitedStack <haomai@unitedstack.com>
++           2015       XSky <haomai@xsky.com>
++           2017       International Business Machines Corp.
++           2015       CohortFS, LLC.
++           2015-2016  Mirantis, Inc.
++           2011-2015 Edward Diener
++           2014 FUJITSU LIMITED
++           2012 Eleanor Cawthon
++           2011-2018, Facebook, Inc.
++           2011 The LevelDB Authors.
++           2016 Mehdi Abaakouk <sileht@redhat.com>
++           2011 Josh Durgin
++           2011 Hannu Valtonen <hannu.valtonen@ormod.com>
++           2015 Hector Martin <marcan@marcan.st>
++License: LGPL2.1 (see COPYING-LGPL2.1)
++
++Files: src/fmt/*
++ src/googletest/*
++ src/isa-l/*
++Copyright: (c) 2012-2018, Victor Zverovich
++ (c) 2019, Paul Dreik
++ (c) 2018, Remotion (Igor Schulz)
++ (c) 2007-2013, Google Inc.
++ (c) 2007, Neal Norwitz
++ 2011-2017 Intel Corporation
++License: BSD 3-clause
++
++Files: src/jaegertracing/thrift/*
++Copyright: (c) 2006-2019, The Apache Software Foundation
++ (c) 2009,2010 Dustin J. Mitchell
++ (c) 2009,2010 Zmanda Inc.
++ (c) 2009 David Reiss
++ (c) 2015 Jens Geyer <jensg@apache.org>
++ (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
++ (c) 2012 Zack Weinberg <zackw@panix.com>
++ (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
++ (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
++ (c) 2015 Paul Norman <penorman@mac.com>
++ (c) 2015 Moritz Klammler <moritz@klammler.eu>
++ (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com>
++ (c) 2011 David Nadlinger
++License: Apache-2.0
++
++Files: src/jaegertracing/jaeger-client-cpp/*
++Copyright: (c) 2017 Uber Technologies, Inc.
++ (c) 2019, The Jaeger Authors
++ (c) 2004, 2006 The Linux Foundation and its contributors.
++ (c) 2012, Twitter Inc.
++License: Apache-2.0
++
++Files: src/jaegertracing/opentracing-cpp/3rd_party/include/opentracing/expected/*
++Copyright: (C) 2016 Martin Moene.
++ (c) 2002-2003 Eric Friedman, Itay Maman
++ (c) 2015 Microsoft Corporation
++License: Expat
++
++Files: src/jaegertracing/opentracing-cpp/3rd_party/include/opentracing/variant/*
++ src/jaegertracing/opentracing-cpp/3rd_party/include/catch2/*
++Copyright: (c) MapBox
++License: BSD 3-clause
++
++Files: src/java/*
++Copyright: Ceph authors
++License: Expat
++
++Files: src/json_spirit/*
++Copyright: 2007-2011, John W. Wilkinson
++License: BSL-1.0
++
++Files: src/libkmip
++Copyright: (c) 2018-2019, The Johns Hopkins University/Applied Physics Laboratory
++License: Apache-2.0
++
++Files: src/liburing
++Copyright: (c) 2019 Jens Axboe
++ (c) 2019 Christoph Hellwig
++License: LGPL2.1 or later
++
++Files: src/rapidjson
++Copyright: (c) 2006-2013 Alexander Chemeris
++ (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip
++License: BSD 3-clause
++
++Files: src/rocksdb/*
++Copyright:  Google Inc.
++ (c) 2011-present, Facebook, Inc.
++License: GPL-2
++
++Files: src/spawn/*
++Copyright: (c) 2006-2019 Google Inc.
++License: BSL-1.0
++
++Files: src/spdk/*
++Copyright: (c) Intel Corporation.
++License: BSD 3 clause
++
++Files: src/xxHash/*
++Copyright: (c) 2012-2014, Yann Collet
++License: BSD 3-clause
++
++Files: src/zstd/*
++Copyright: (c) 2016, Facebook, Inc. All rights reserved.
++License: BSD 3-clause
++
++Files: src/seastar/*
++Copyright: (C) 2016-2020, ScyllaDB
++ (c) 2017-2019, Red Hat, Inc.
++ (c) 2014-2020, Cloudius Systems, Ltd.
++ (c) 2010-2018, Intel Corporation
++ (c) 2003-2013, Christopher M. Kohlhoff
++ (c) 2012-2018, 6WIND S.A.
++ (c) 2018, Luca Boccassi <bluca@debian.org>
++ (c) 2010, David Malone <dwmalone@FreeBSD.org>
++ (c) 2019, Elazar Leibovich
++ (c) 2013, Prometheus Team
++ (c) 2017-2018, Marvell International Ltd.
++ (c) 2014-2017, IBM Corporation
++ (c) 2015-2018, RehiveTech
++ (c) 2015-2018, CESNET
++ (c) 2015-2018, Cavium, Inc
++ (c) 2016-2018, NXP
++ (c) 2000-2018, Kitware, Inc. and Contributors
++ (c) 2017, Marek Waszkiewicz <marek.waszkiewicz77@gmail.com>
++ (C) 2019, Lightbits Labs Ltd.
++ (c) 2016, Canonical Limited.
++ (c) 2019, Netcope Technologies, a.s. <info@netcope.com>
++ (c) 2015-2018, Atomic Rules LLC
++ (c) 2015-2016, QLogic Corporation
++ (c) 2017-2018, Semihalf.
++ (c) 2013-2018, Broadcom
++ (c) 2008-2018, Cisco Systems, Inc.
++ (c) 2014-2016, Freescale Semiconductor, Inc.
++ (c) 2013-2017, Wind River Systems, Inc.
++ (c) 2015-2018, Mellanox Technologies, Ltd
++ (c) 2018-2019, Arm Limited
++ (c) 2019, Cesnet
++ (c) 2014-2018, Chelsio Communications.
++ (c) 2018, Aquantia Corporation
++ (c) 2018, Microsoft Corporation.
++ (c) 2018, The DPDK contributors
++ (c) 2017-2018, Solarflare Communications Inc.
++ (c) 2015-2016, Amazon.com, Inc.
++ (c) 2014-2018, Netronome Systems, Inc.
++ (c) 2016, IGEL Co., Ltd.
++ (c) 2018, Advanced Micro Devices, Inc.
++ (c) 2018, ARM Corporation.
++ (c) 2014-2017, aQuantia Corporation.
++ (c) 2018, Synopsys, Inc. All rights reserved.
++ (c) 2007, Nuova Systems, Inc.  All rights reserved.
++ (c) 2013-2015, Brocade Communications Systems, Inc.
++ (c) 2012, NetApp Inc.
++ (c) 2010, Jonathan Armani <armani@openbsd.org>
++ (c) 2010, Fabien Romano <fabien@openbsd.org>
++ (c) 2010, Michael Knudsen <mk@openbsd.org>
++License: Apache-2.0
++Comment:
++ The dpdk folder is under various folders:
++ bsd-3-clause, gpl-2.0 and lgpl-2.1.
++
++Files: src/common/crc32c_intel_baseline.c
++Copyright: 2012-2013 Intel Corporation
++License: BSD 3-clause
++
++Files: src/crimson/common/layout.h
++Copyright: 2018 The Abseil Authors
++License: Apache-2.0
++
++Files: cmake/modules/FindPython3.cmake cmake/modules/FindBacktrace.cmake
++ cmake/modules/FindBoost.cmake cmake/modules/FindPython/Support.cmake
++Copyright: (c) 2013 Vadim Zhukov
++License: BSD 3-clause
++
++Files: cmake/modules/Findblkid.cmake
++Copyright: (c) 2007-2012 Hypertable, Inc.
++License: GPL-3+
++
++Files: cmake/modules/FindLTTngUST.cmake
++Copyright:
++    Copyright 2016 Kitware, Inc.
++    Copyright 2016 Philippe Proulx <pproulx@efficios.com>
++License: BSD 3-clause
++
++Files: make-debs.sh
++Copyright: (c) Loic Dachary <loic@dachary.org>
++ (c) 2015 Red Hat <contact@redhat.com>
++License: GPL-2+
++
++Files: fusetrace/fusetrace_ll.cc
++Copyright: (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
++License: GPL-2
++
++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: src/boost/*
++Copyright: Boost project contributors
++License: BSL-1.0
++Comment:
++ Ceph embedds boost sources, which has complex copyright. Rather than
++ duplicating its debian copyright, this package relies on the package
++ maintainer of Boost for this work.
++ .
++ Please see /usr/share/doc/libboost*/copyright fore more details.
++
++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: BSL-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: BSL-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: BSL-1.0
++
++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
++
++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: Expat
++ 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.
++
++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".
++
++
++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: GPL-3+
++ 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 3 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, see <http://www.gnu.org/licenses/>.
++ .
++ On Debian systems, the complete text of the GNU General Public
++ License version 3 can be found in `/usr/share/common-licenses/GPL-3'.
++
++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 --cc debian/gbp.conf
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..023aff39f844d1c82c15ed7300ab4e064f77bd5f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++[DEFAULT]
++debian-branch = debian/unstable
++pristine-tar = True
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cf62f8321ca251a9d3e4c8ba8c3e025fb50fbf78
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,12 @@@
++#!/bin/sh
++
++#/lib/systemd/system-sleep/ceph
++
++case $1 in
++pre)
++    /bin/systemctl stop ceph
++;;
++post)
++    /bin/systemctl start ceph
++;;
++esac
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4e29bc1e9d1833c4a7ff3a0a48c4651afe5cb45a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..86ff057f1a02e6e4113b4fcc3f6c50084de6e70f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d89c74a64faec28cb767e168dd9b2d4057fdfc96
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..adfa6a02fef12f774c81d4c5e824c432dbaefdca
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a23c5b057f2fde1cec8db5dfb2b30c58ba9ee011
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/include/cephfs/*.h
++usr/lib/*/libcephfs.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1029fb53d40fdb7f7bd696d8edc76c6f9351d817
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++debian/tmp/usr/share/java/libcephfs.jar
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..15cb91d907bb3684ae8292808f466b0117962603
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/libcephfs_jni.so* usr/lib/jni
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..93a0dacafeb8097ed57b73c44f109c25bc9409f5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++binary-or-shlib-defines-rpath usr/lib/jni/*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f09e93ca5af1b71c0436fb47ecc62564035591e5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/libcephfs.so.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d4041ca8e38f6d6b87374fdde45dd17d17748cba
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++# False-positives:
++spelling-error-in-binary * tEH the
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1e5a14a8d1c61a698e4735aba136170a5887f1ad
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,212 @@@
++libcephfs.so.2 libcephfs2 #MINVER#
++ (regex|c++)"^_.*" 12.0.3
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEE6_M_runEv@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED0Ev@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED1Ev@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED2Ev@Base 16.2.6+ds
++ _ZTINSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ _ZTSNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ _ZTVNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ 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_chmodat@Base 16.2.6+ds
++ ceph_chown@Base 12.0.3
++ ceph_chownat@Base 16.2.6+ds
++ 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_fdopendir@Base 16.2.6+ds
++ 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_free_snap_info_buffer@Base 16.2.6+ds
++ 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_snap_info@Base 16.2.6+ds
++ ceph_get_stripe_unit_granularity@Base 12.0.3
++ ceph_getaddrs@Base 16.2.6+ds
++ 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_lchmod@Base 16.2.6+ds
++ 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_lookup_vino@Base 16.2.6+ds
++ 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_may_delete@Base 16.2.6+ds
++ ceph_mds_command@Base 12.0.3
++ ceph_mkdir@Base 12.0.3
++ ceph_mkdirat@Base 16.2.6+ds
++ ceph_mkdirs@Base 12.0.3
++ ceph_mknod@Base 12.0.3
++ ceph_mksnap@Base 16.2.6+ds
++ 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_openat@Base 16.2.6+ds
++ 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_readlinkat@Base 16.2.6+ds
++ 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_rmsnap@Base 16.2.6+ds
++ 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_mount_timeout@Base 16.2.6+ds
++ 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_statxat@Base 16.2.6+ds
++ ceph_symlink@Base 12.0.3
++ ceph_symlinkat@Base 16.2.6+ds
++ 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_unlinkat@Base 16.2.6+ds
++ 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_utimensat@Base 16.2.6+ds
++ ceph_utimes@Base 14.2.0
++ ceph_version@Base 12.0.3
++ ceph_write@Base 12.0.3
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..edaa359fc6a1f269868fccba1ed9e76c8b1bb10e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2020e29c45b936c4328f2824140f20e9a5e10d2f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/*/ceph/libceph-common.so*
++usr/lib/*/librados.so.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d4041ca8e38f6d6b87374fdde45dd17d17748cba
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++# False-positives:
++spelling-error-in-binary * tEH the
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..749cdd772d6fcbb8b256685df2425ba631fe24fd
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4d6d7b5a0936428a2890ff22b310986d30cda560
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++usr/include/radosstriper/libradosstriper.h
++usr/include/radosstriper/libradosstriper.hpp
++usr/lib/*/libradosstriper.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..742549ba9701995aab422598d2b2267394817ca4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/libradosstriper.so.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6da2d7c44f7c579720515508eec6c3c4681530f8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,47 @@@
++libradosstriper.so.1 libradosstriper1 #MINVER#
++ (regex|c++)"^_.*" 0.87
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEE6_M_runEv@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED0Ev@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED1Ev@Base 16.2.6+ds
++ _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEED2Ev@Base 16.2.6+ds
++ _ZTINSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ _ZTSNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ _ZTVNSt6thread11_State_implINS_8_InvokerISt5tupleIJZ17make_named_threadIZN4ceph5async15io_context_pool5startEsEUlvE_JEES_St17basic_string_viewIcSt11char_traitsIcEEOT_DpOT0_EUlSD_SG_E_S7_EEEEEE@Base 16.2.6+ds
++ boost_asio_detail_posix_thread_function@Base 16.2.6+ds
++ 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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c6b455e58d91303799725b5cdb0fa7ab8ee14b02
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4 @@@
++usr/include/rbd/features.h
++usr/include/rbd/librbd.h
++usr/include/rbd/librbd.hpp
++usr/lib/*/librbd.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..decadbc194bc3edbdd9f99821ab958c6c5b4ec9b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/librbd.so.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f6e6c33680176d31d16b028cd8d68fe7e2b91be1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,281 @@@
++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_create_snapshot@Base 16.2.6+ds
++ rbd_aio_mirror_image_demote@Base 12.0.3
++ rbd_aio_mirror_image_get_global_status@Base 16.2.6+ds
++ rbd_aio_mirror_image_get_info@Base 12.0.3
++ rbd_aio_mirror_image_get_mode@Base 16.2.6+ds
++ 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_encryption_format@Base 16.2.6+ds
++ rbd_encryption_load@Base 16.2.6+ds
++ rbd_features_from_string@Base 16.2.6+ds
++ rbd_features_to_string@Base 16.2.6+ds
++ 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_migration_source_spec@Base 16.2.6+ds
++ 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_create2@Base 16.2.6+ds
++ 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_prepare_import@Base 16.2.6+ds
++ rbd_migration_status@Base 14.2.0
++ rbd_migration_status_cleanup@Base 14.2.0
++ rbd_mirror_image_create_snapshot2@Base 16.2.6+ds
++ rbd_mirror_image_create_snapshot@Base 16.2.6+ds
++ rbd_mirror_image_demote@Base 10.1.0
++ rbd_mirror_image_disable@Base 10.1.0
++ rbd_mirror_image_enable2@Base 16.2.6+ds
++ rbd_mirror_image_enable@Base 10.1.0
++ rbd_mirror_image_get_global_status@Base 16.2.6+ds
++ rbd_mirror_image_get_info@Base 10.1.0
++ rbd_mirror_image_get_info_cleanup@Base 16.2.6+ds
++ rbd_mirror_image_get_instance_id@Base 14.2.0
++ rbd_mirror_image_get_mode@Base 16.2.6+ds
++ rbd_mirror_image_get_status@Base 12.0.3
++ rbd_mirror_image_global_status_cleanup@Base 16.2.6+ds
++ rbd_mirror_image_global_status_list@Base 16.2.6+ds
++ rbd_mirror_image_global_status_list_cleanup@Base 16.2.6+ds
++ rbd_mirror_image_info_list@Base 16.2.6+ds
++ rbd_mirror_image_info_list_cleanup@Base 16.2.6+ds
++ 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_peer_site_add@Base 16.2.6+ds
++ rbd_mirror_peer_site_get_attributes@Base 16.2.6+ds
++ rbd_mirror_peer_site_list@Base 16.2.6+ds
++ rbd_mirror_peer_site_list_cleanup@Base 16.2.6+ds
++ rbd_mirror_peer_site_remove@Base 16.2.6+ds
++ rbd_mirror_peer_site_set_attributes@Base 16.2.6+ds
++ rbd_mirror_peer_site_set_client_name@Base 16.2.6+ds
++ rbd_mirror_peer_site_set_direction@Base 16.2.6+ds
++ rbd_mirror_peer_site_set_name@Base 16.2.6+ds
++ rbd_mirror_site_name_get@Base 14.2.5
++ rbd_mirror_site_name_set@Base 14.2.5
++ rbd_mirror_uuid_get@Base 16.2.6+ds
++ 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_quiesce_unwatch@Base 16.2.6+ds
++ rbd_quiesce_watch@Base 16.2.6+ds
++ 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_create2@Base 16.2.6+ds
++ rbd_snap_create@Base 0.72.2
++ rbd_snap_exists@Base 16.2.6+ds
++ rbd_snap_get_group_namespace@Base 13.2.0
++ rbd_snap_get_id@Base 16.2.6+ds
++ rbd_snap_get_limit@Base 12.0.3
++ rbd_snap_get_mirror_namespace@Base 16.2.6+ds
++ rbd_snap_get_name@Base 16.2.6+ds
++ 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_mirror_namespace_cleanup@Base 16.2.6+ds
++ 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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..56d4da0085d25e5e59ae6f5f779e0db81b1c251d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++usr/include/rados/librgw.h
++usr/include/rados/rgw_file.h
++usr/lib/*/librgw.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..267c561138b833c8529b7bc6c0345020cc22331f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/librgw.so.*
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8c696d069e86aa9f945c9c4c7f4efa473993c277
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/include/libcephsqlite.h
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c33877245b24674188d73e5b8ac5b562736e8778
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/lib/*/libcephsqlite.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d05d85c7b805057e47c8bc9decef67b990668564
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..718936578c5d0a35df31e88ed23304f8c54a3848
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8a2e99a535d47e5798b167d1074ae2c77cab21e7
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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);
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..859d3b418a36691cc4cbd63faf392420dd2da4b3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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);
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cec515eec4854bad2686229cb2dd210167eaee5f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,148 @@@
++Description: Misc fixes for 32 bit architecture builds.
++Author: James Page <james.page@ubuntu.com>
++Forwarded: no
++
++--- a/src/tools/rbd_mirror/image_replayer/snapshot/Replayer.cc
+++++ b/src/tools/rbd_mirror/image_replayer/snapshot/Replayer.cc
++@@ -253,7 +253,8 @@ bool Replayer<I>::get_replay_status(std:
++ 
++   json_spirit::mObject root_obj;
++   root_obj["replay_state"] = replay_state;
++-  root_obj["remote_snapshot_timestamp"] = remote_snap_info->timestamp.sec();
+++  root_obj["remote_snapshot_timestamp"] = static_cast<uint64_t>(
+++    remote_snap_info->timestamp.sec());
++ 
++   auto matching_remote_snap_id = util::compute_remote_snap_id(
++     m_state_builder->local_image_ctx->image_lock,
++@@ -267,8 +268,8 @@ bool Replayer<I>::get_replay_status(std:
++     // use the timestamp from the matching remote image since
++     // the local snapshot would just be the time the snapshot was
++     // synced and not the consistency point in time.
++-    root_obj["local_snapshot_timestamp"] =
++-      matching_remote_snap_it->second.timestamp.sec();
+++    root_obj["local_snapshot_timestamp"] = static_cast<uint64_t>(
+++      matching_remote_snap_it->second.timestamp.sec());
++   }
++ 
++   matching_remote_snap_it = m_state_builder->remote_image_ctx->snap_info.find(
++@@ -276,7 +277,8 @@ bool Replayer<I>::get_replay_status(std:
++   if (m_remote_snap_id_end != CEPH_NOSNAP &&
++       matching_remote_snap_it !=
++         m_state_builder->remote_image_ctx->snap_info.end()) {
++-    root_obj["syncing_snapshot_timestamp"] = remote_snap_info->timestamp.sec();
+++    root_obj["syncing_snapshot_timestamp"] = static_cast<uint64_t>(
+++        remote_snap_info->timestamp.sec());
++     root_obj["syncing_percent"] = static_cast<uint64_t>(
++         100 * m_local_mirror_snap_ns.last_copied_object_number /
++         static_cast<float>(std::max<uint64_t>(1U, m_local_object_count)));
++--- a/src/common/buffer.cc
+++++ b/src/common/buffer.cc
++@@ -2272,7 +2272,7 @@ MEMPOOL_DEFINE_OBJECT_FACTORY(buffer::ra
++ 
++ void ceph::buffer::list::page_aligned_appender::_refill(size_t len) {
++   const size_t alloc = \
++-    std::max((size_t)min_alloc, (len + CEPH_PAGE_SIZE - 1) & CEPH_PAGE_MASK);
+++    std::max((size_t)min_alloc, (size_t)((len + CEPH_PAGE_SIZE - 1) & CEPH_PAGE_MASK));
++   auto new_back = \
++     ptr_node::create(buffer::create_page_aligned(alloc));
++   new_back->set_length(0);   // unused, so far.
++--- a/src/s3select/include/s3select_functions.h
+++++ b/src/s3select/include/s3select_functions.h
++@@ -585,7 +585,7 @@ struct _fn_diff_timestamp : public base_
++     {
++       boost::gregorian::date_period dp =
++         boost::gregorian::date_period( val_dt1.timestamp()->date(), val_dt2.timestamp()->date());
++-      result->set_value( dp.length().days() );
+++      result->set_value( (int64_t)dp.length().days() );
++     }
++     else if (strcmp(val_date_part.str(), "hours") == 0)
++     {
++--- a/src/os/bluestore/BlueFS.cc
+++++ b/src/os/bluestore/BlueFS.cc
++@@ -3787,11 +3787,11 @@ int BlueFS::do_replay_recovery_read(File
++ 
++ size_t BlueFS::probe_alloc_avail(int dev, uint64_t alloc_size)
++ {
++-  size_t total = 0;
++-  auto iterated_allocation = [&](size_t off, size_t len) {
+++  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
++--- a/src/tools/neorados.cc
+++++ b/src/tools/neorados.cc
++@@ -146,7 +146,7 @@ void create(R::RADOS& r, const std::vect
++                           obj, pname));
++ }
++ 
++-inline constexpr std::size_t io_size = 4 << 20;
+++inline constexpr std::uint64_t io_size = 4 << 20;
++ 
++ void write(R::RADOS& r, const std::vector<std::string>& p, s::yield_context y)
++ {
++@@ -156,7 +156,7 @@ void write(R::RADOS& r, const std::vecto
++ 
++   bs::error_code ec;
++   std::unique_ptr<char[]> buf = std::make_unique<char[]>(io_size);
++-  std::size_t off = 0;
+++  std::uint64_t off = 0;
++   boost::io::ios_exception_saver ies(std::cin);
++ 
++   std::cin.exceptions(std::istream::badbit);
++@@ -203,7 +203,7 @@ void read(R::RADOS& r, const std::vector
++                  obj, pname));
++   }
++ 
++-  std::size_t off = 0;
+++  std::uint64_t off = 0;
++   ceph::buffer::list bl;
++   while (auto toread = std::max(len - off, io_size)) {
++     R::ReadOp op;
++--- a/src/tools/cephfs_mirror/FSMirror.cc
+++++ b/src/tools/cephfs_mirror/FSMirror.cc
++@@ -345,7 +345,7 @@ void FSMirror::handle_acquire_directory(
++     std::scoped_lock locker(m_lock);
++     m_directories.emplace(dir_path);
++     m_service_daemon->add_or_update_fs_attribute(m_filesystem.fscid, SERVICE_DAEMON_DIR_COUNT_KEY,
++-                                                 m_directories.size());
+++                                                 static_cast<uint64_t>(m_directories.size()));
++ 
++     for (auto &[peer, peer_replayer] : m_peer_replayers) {
++       dout(10) << ": peer=" << peer << dendl;
++@@ -363,7 +363,7 @@ void FSMirror::handle_release_directory(
++     if (it != m_directories.end()) {
++       m_directories.erase(it);
++       m_service_daemon->add_or_update_fs_attribute(m_filesystem.fscid, SERVICE_DAEMON_DIR_COUNT_KEY,
++-                                                   m_directories.size());
+++                                                   static_cast<uint64_t>(m_directories.size()));
++       for (auto &[peer, peer_replayer] : m_peer_replayers) {
++         dout(10) << ": peer=" << peer << dendl;
++         peer_replayer->remove_directory(dir_path);
++--- a/src/librbd/object_map/DiffRequest.cc
+++++ b/src/librbd/object_map/DiffRequest.cc
++@@ -175,7 +175,7 @@ void DiffRequest<I>::handle_load_object_
++     m_object_map.resize(num_objs);
++   }
++ 
++-  size_t prev_object_diff_state_size = m_object_diff_state->size();
+++  uint64_t prev_object_diff_state_size = m_object_diff_state->size();
++   if (prev_object_diff_state_size < num_objs) {
++     // the diff state should be the largest of all snapshots in the set
++     m_object_diff_state->resize(num_objs);
++--- a/src/SimpleRADOSStriper.cc
+++++ b/src/SimpleRADOSStriper.cc
++@@ -140,7 +140,7 @@ int SimpleRADOSStriper::remove()
++   return 0;
++ }
++ 
++-int SimpleRADOSStriper::truncate(uint64_t size)
+++int SimpleRADOSStriper::truncate(size_t size)
++ {
++   d(5) << size << dendl;
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1cb0d2f2c9a4cd772d6f663db28ebf1791a55f07
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,134 @@@
++From: Shengjing Zhu <zhushengjing@cambricon.com>
++Date: Sun, 31 Jul 2022 15:27:17 +0800
++Subject: Fix build with fmt 8/9
++
+++ changes in segment_manager.cc and segment_manager.h are backported from
++  part of the large changes in https://github.com/ceph/ceph/commit/d5b0cd13
+++ change in node_extent_accessor.h is not forwarded to upstream since it's
++  a workaround. However it doesn't harm since it's just a error message
++  which shouldn't happen anyway.
+++ changes in seastar is backported from
++  https://github.com/scylladb/seastar/commit/dfb62861
+++ changes in crimson/osd/main.cc is backported from
++  https://github.com/ceph/ceph/commit/58cb9bac
++---
++ src/crimson/os/seastore/CMakeLists.txt                |  1 +
++ .../staged-fltree/node_extent_accessor.h              |  2 +-
++ src/crimson/os/seastore/segment_manager.cc            | 19 +++++++++++++++++++
++ src/crimson/os/seastore/segment_manager.h             |  3 +++
++ src/crimson/osd/main.cc                               |  6 +++---
++ src/seastar/include/seastar/core/print.hh             |  4 ++++
++ 6 files changed, 31 insertions(+), 4 deletions(-)
++ create mode 100644 src/crimson/os/seastore/segment_manager.cc
++
++diff --git a/src/crimson/os/seastore/CMakeLists.txt b/src/crimson/os/seastore/CMakeLists.txt
++index 77f8465..c6d4e93 100644
++--- a/src/crimson/os/seastore/CMakeLists.txt
+++++ b/src/crimson/os/seastore/CMakeLists.txt
++@@ -1,6 +1,7 @@
++ add_library(crimson-seastore STATIC
++   cached_extent.cc
++   seastore_types.cc
+++  segment_manager.cc
++   segment_manager/ephemeral.cc
++   segment_manager/block.cc
++   transaction_manager.cc
++diff --git a/src/crimson/os/seastore/onode_manager/staged-fltree/node_extent_accessor.h b/src/crimson/os/seastore/onode_manager/staged-fltree/node_extent_accessor.h
++index 94782f5..3c45861 100644
++--- a/src/crimson/os/seastore/onode_manager/staged-fltree/node_extent_accessor.h
+++++ b/src/crimson/os/seastore/onode_manager/staged-fltree/node_extent_accessor.h
++@@ -169,7 +169,7 @@ class DeltaRecorderT final: public DeltaRecorder {
++       }
++       default:
++         logger().error("OTree::Extent::Replay: got unknown op {} when replay {:#x}",
++-                       op, node.get_laddr());
+++                       static_cast<uint8_t>(op), node.get_laddr());
++         ceph_abort();
++       }
++     } catch (buffer::error& e) {
++diff --git a/src/crimson/os/seastore/segment_manager.cc b/src/crimson/os/seastore/segment_manager.cc
++new file mode 100644
++index 0000000..d4a7132
++--- /dev/null
+++++ b/src/crimson/os/seastore/segment_manager.cc
++@@ -0,0 +1,19 @@
+++#include "crimson/os/seastore/segment_manager.h"
+++
+++namespace crimson::os::seastore {
+++
+++  std::ostream& operator<<(std::ostream &out, Segment::segment_state_t s)
+++  {
+++    using state_t = Segment::segment_state_t;
+++    switch (s) {
+++      case state_t::EMPTY:
+++        return out << "EMPTY";
+++      case state_t::OPEN:
+++        return out << "OPEN";
+++      case state_t::CLOSED:
+++        return out << "CLOSED";
+++      default:
+++        return out << "INVALID_SEGMENT_STATE!";
+++    }
+++  }
+++}
++diff --git a/src/crimson/os/seastore/segment_manager.h b/src/crimson/os/seastore/segment_manager.h
++index 61c6509..30bdbc4 100644
++--- a/src/crimson/os/seastore/segment_manager.h
+++++ b/src/crimson/os/seastore/segment_manager.h
++@@ -73,6 +73,9 @@ public:
++ 
++   virtual ~Segment() {}
++ };
+++
+++std::ostream& operator<<(std::ostream& out, Segment::segment_state_t);
+++
++ using SegmentRef = boost::intrusive_ptr<Segment>;
++ 
++ constexpr size_t PADDR_SIZE = sizeof(paddr_t);
++diff --git a/src/crimson/osd/main.cc b/src/crimson/osd/main.cc
++index a90903e..0db6496 100644
++--- a/src/crimson/osd/main.cc
+++++ b/src/crimson/osd/main.cc
++@@ -88,7 +88,7 @@ seastar::future<> make_keyring()
++     if (exists &&
++         keyring.load(nullptr, path) == 0 &&
++         keyring.get_auth(name, auth)) {
++-      seastar::fprint(std::cerr, "already have key in keyring: %s\n", path);
+++      fmt::print(std::cerr, "already have key in keyring: %s\n", path);
++       return seastar::now();
++     } else {
++       auth.key.create(std::make_unique<CephContext>().get(), CEPH_CRYPTO_AES);
++@@ -100,7 +100,7 @@ seastar::future<> make_keyring()
++       return crimson::write_file(std::move(bl), path, permissions);
++     }
++   }).handle_exception_type([path](const std::filesystem::filesystem_error& e) {
++-    seastar::fprint(std::cerr, "FATAL: writing new keyring to %s: %s\n", path, e.what());
+++    fmt::print(std::cerr, "FATAL: writing new keyring to %s: %s\n", path, e.what());
++     throw e;
++   });
++ }
++@@ -216,7 +216,7 @@ int main(int argc, char* argv[])
++       });
++     });
++   } catch (...) {
++-    seastar::fprint(std::cerr, "FATAL: Exception during startup, aborting: %s\n", std::current_exception());
+++    fmt::print(std::cerr, "FATAL: Exception during startup, aborting: %s\n", std::current_exception());
++     return EXIT_FAILURE;
++   }
++ }
++diff --git a/src/seastar/include/seastar/core/print.hh b/src/seastar/include/seastar/core/print.hh
++index 72e3934..c1868e8 100644
++--- a/src/seastar/include/seastar/core/print.hh
+++++ b/src/seastar/include/seastar/core/print.hh
++@@ -133,7 +133,11 @@ template <typename... A>
++ sstring
++ format(const char* fmt, A&&... a) {
++     fmt::memory_buffer out;
+++#if FMT_VERSION >= 80000
+++    fmt::format_to(fmt::appender(out), fmt::runtime(fmt), std::forward<A>(a)...);
+++#else
++     fmt::format_to(out, fmt, std::forward<A>(a)...);
+++#endif
++     return sstring{out.data(), out.size()};
++ }
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ef813e5054463462cb029bed81480f69f508244a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,12 @@@
++Index: ceph/src/tools/CMakeLists.txt
++===================================================================
++--- ceph.orig/src/tools/CMakeLists.txt
+++++ ceph/src/tools/CMakeLists.txt
++@@ -151,5 +151,7 @@ if(WITH_SEASTAR)
++ endif()
++ 
++ add_subdirectory(immutable_object_cache)
+++if(NOT DISABLE_DENCODER)
++ add_subdirectory(ceph-dencoder)
+++endif(DISABLE_DENCODER)
++ add_subdirectory(erasure-code)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..763a948f9f159d9a4ea2cd2b9e56a6d4e19b0369
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,49 @@@
++From a8c1aec073fc8364818027a26fa1ddb5d34c58af Mon Sep 17 00:00:00 2001
++From: Matthew Vernon <mv3@sanger.ac.uk>
++Date: Thu, 4 Feb 2021 11:41:14 +0000
++Subject: [PATCH] rgw/radosgw-admin clarify error when email address already in
++ use
++
++The error message if you try and create an S3 user with an email
++address that is already associated with another S3 account is very
++confusing; this patch makes it much clearer
++
++To reproduce:
++
++radosgw-admin user create --uid=foo --display-name="Foo test" --email=bar@domain.invalid
++radosgw-admin user create --uid=test --display-name="AN test" --email=bar@domain.invalid
++could not create user: unable to parse parameters, user id mismatch, operation id: foo does not match: test
++
++With this patch:
++
++radosgw-admin user create --uid=test --display-name="AN test" --email=bar@domain.invalid
++could not create user: unable to create user test because user id foo already exists with email bar@domain.invalid
++
++Fixes: https://tracker.ceph.com/issues/49137
++Fixes: https://tracker.ceph.com/issues/19411
++Signed-off-by: Matthew Vernon <mv3@sanger.ac.uk>
++(cherry picked from commit 05318d6f71e45a42a46518a0ef17047dfab83990)
++---
++ src/rgw/rgw_user.cc | 9 ++++++++-
++ 1 file changed, 8 insertions(+), 1 deletion(-)
++
++Index: ceph/src/rgw/rgw_user.cc
++===================================================================
++--- ceph.orig/src/rgw/rgw_user.cc
+++++ ceph/src/rgw/rgw_user.cc
++@@ -1970,7 +1970,14 @@ int RGWUser::remove(const DoutPrefixProv
++ 
++   ret = check_op(op_state, &subprocess_msg);
++   if (ret < 0) {
++-    set_err_msg(err_msg, "unable to parse parameters, " + subprocess_msg);
+++    if (is_populated() && (user_id.compare(op_state.get_user_id()) != 0)) {
+++      set_err_msg(err_msg, "unable to create user " + user_id.to_str()
+++               + " because user id " + op_state.get_user_id().to_str()
+++               + " already exists with email "
+++               + op_state.get_user_email());
+++    } else {
+++      set_err_msg(err_msg, "unable to parse parameters, " + subprocess_msg);
+++    }
++     return ret;
++   }
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..aca739fb9f28ee6660e2af7660b61e03741025ed
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,143 @@@
++From db463aa139aa0f3eb996062bd7c65f0d10a7932b Mon Sep 17 00:00:00 2001
++From: luo rixin <luorixin@huawei.com>
++Date: Fri, 8 Jan 2021 16:16:02 +0800
++Subject: [PATCH] src/isa-l/erasure_code: Fix text relocation on aarch64
++
++Here is the bug report on ceph. https://tracker.ceph.com/issues/48681
++
++Signed-off-by: luo rixin <luorixin@huawei.com>
++---
++ src/isa-l/erasure_code/aarch64/gf_2vect_mad_neon.S | 5 +++--
++ src/isa-l/erasure_code/aarch64/gf_3vect_mad_neon.S | 5 +++--
++ src/isa-l/erasure_code/aarch64/gf_4vect_mad_neon.S | 5 +++--
++ src/isa-l/erasure_code/aarch64/gf_5vect_mad_neon.S | 5 +++--
++ src/isa-l/erasure_code/aarch64/gf_6vect_mad_neon.S | 5 +++--
++ src/isa-l/erasure_code/aarch64/gf_vect_mad_neon.S  | 5 +++--
++ 6 files changed, 18 insertions(+), 12 deletions(-)
++
++--- a/src/isa-l/erasure_code/aarch64/gf_2vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_2vect_mad_neon.S
++@@ -360,7 +360,8 @@ gf_2vect_mad_neon:
++      sub     x_dest1, x_dest1, x_tmp
++      sub     x_dest2, x_dest2, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -394,7 +395,7 @@ gf_2vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
++--- a/src/isa-l/erasure_code/aarch64/gf_3vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_3vect_mad_neon.S
++@@ -332,7 +332,8 @@ gf_3vect_mad_neon:
++      sub     x_dest2, x_dest2, x_tmp
++      sub     x_dest3, x_dest3, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -374,7 +375,7 @@ gf_3vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
++--- a/src/isa-l/erasure_code/aarch64/gf_4vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_4vect_mad_neon.S
++@@ -397,7 +397,8 @@ gf_4vect_mad_neon:
++      sub     x_dest3, x_dest3, x_tmp
++      sub     x_dest4, x_dest4, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -448,7 +449,7 @@ gf_4vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
++--- a/src/isa-l/erasure_code/aarch64/gf_5vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_5vect_mad_neon.S
++@@ -463,7 +463,8 @@ gf_5vect_mad_neon:
++      sub     x_dest4, x_dest4, x_tmp
++      sub     x_dest5, x_dest5, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -527,7 +528,7 @@ gf_5vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
++--- a/src/isa-l/erasure_code/aarch64/gf_6vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_6vect_mad_neon.S
++@@ -526,7 +526,8 @@ gf_6vect_mad_neon:
++      sub     x_dest5, x_dest5, x_tmp
++      sub     x_dest6, x_dest6, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -602,7 +603,7 @@ gf_6vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
++--- a/src/isa-l/erasure_code/aarch64/gf_vect_mad_neon.S
+++++ b/src/isa-l/erasure_code/aarch64/gf_vect_mad_neon.S
++@@ -281,7 +281,8 @@ gf_vect_mad_neon:
++      mov     x_src, x_src_end
++      sub     x_dest1, x_dest1, x_tmp
++ 
++-     ldr     x_const, =const_tbl
+++     adrp    x_const, const_tbl
+++     add     x_const, x_const, :lo12:const_tbl
++      sub     x_const, x_const, x_tmp
++      ldr     q_tmp, [x_const, #16]
++ 
++@@ -307,7 +308,7 @@ gf_vect_mad_neon:
++      mov     w_ret, #1
++      ret
++ 
++-.section .data
+++.section .rodata
++ .balign 8
++ const_tbl:
++      .dword 0x0000000000000000, 0x0000000000000000
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8313b3ecea591bb305776e0d04c732d149bc7571
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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)",
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e8e3b0b0f0c26390253645181ecc84f938046354
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cdd27b5ab0a34ebf3acf8eb16b9417a88a07cd6f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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",
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c9712311abd0a1e0dad404765a968995a3dbf984
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,29 @@@
++Description: cmake: test for 16-byte atomic support on mips also
++ it's reported that a mips64el build host is able to pass the test of
++ CheckCxxAtomic without linking against libatomic, while librbd.so
++ fails to link due to failures like
++ .
++ /usr/bin/ld: ../../../lib/librbd.so.1.16.0: undefined reference to `__atomic_store_16'
++ /usr/bin/ld: ../../../lib/librbd.so.1.16.0: undefined reference to `__atomic_load_16'
++ /usr/bin/ld: ../../../lib/librbd.so.1.16.0: undefined reference to `__atomic_compare_exchange_16'
++ .
++ so we have to check the existence of __atomic_load_16 instruction on
++ mips architecture.
++Author: Kefu Chai <tchaikov@gmail.com>
++Date: Wed, 24 Nov 2021 00:40:54 +0800
++Origin: upstream, https://github.com/ceph/ceph/commit/709a77f22010f03aee4a4c0ab930588944cb4a58
++Last-Update: 2021-11-24
++
++diff --git a/cmake/modules/CheckCxxAtomic.cmake b/cmake/modules/CheckCxxAtomic.cmake
++index f2d89cf3e0beb..da2be5206d634 100644
++--- a/cmake/modules/CheckCxxAtomic.cmake
+++++ b/cmake/modules/CheckCxxAtomic.cmake
++@@ -11,7 +11,7 @@ function(check_cxx_atomics var)
++ #include <atomic>
++ #include <cstdint>
++ 
++-#if __s390x__
+++#if defined(__s390x__) || (defined(__mips__) && _MIPS_SIM ==_ABI64 )
++ // Boost needs 16-byte atomics for tagged pointers.
++ // These are implemented via inline instructions on the platform
++ // if 16-byte alignment can be proven, and are delegated to libatomic
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..888d58919aed54cbb8b7f5cfb55ebf152f2aec19
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,92 @@@
++From 1b9ad6ca971d2c6222f1fb405ae620a32159cd5d Mon Sep 17 00:00:00 2001
++From: Kefu Chai <tchaikov@gmail.com>
++Date: Sun, 29 Aug 2021 22:24:30 +0800
++Subject: [PATCH] arch,cmake: compile ppc.c on all powerpc machines
++
++* cmake/modules/SIMDExt.cmake: define HAVE_PPC for 32-bit PowerPC.
++* src/arch/CMakeLists.txt: compile ppc.c for all PowerPC architectures,
++  including powerpc (32-bit PowerPC), ppc64el (64-bit Little Endian
++  PowerPC) and ppc64 (64-bit Big Endian PowerPC).
++
++before this change, ppc.c is only compiled if HAVE_POWER8 is defined.
++but Power8 is a 64-bit PowerPC architecture. while in src/arch/probe.cc,
++we check for `defined(__powerpc__) || defined(__ppc__)`, if this is
++true, ceph_arch_ppc_probe() is used to check for the support of
++Altivec. but on non-power8 PowerPC machines, the linker fails to find the
++symbols like ceph_arch_ppc_probe(), as ppc.c is not compiled on them.
++
++in this change, ppc.c is compiled on all PowerPC architectures, so that
++ceph_arch_ppc_probe() is also available on non-power8 machines. this
++change does not impact the behavior of non-power8 machines. because
++on them, the runtime check would fail to detect the existence of
++PPC_FEATURE2_VEC_CRYPTO instructions.
++
++Reported-by: Mattias Ellert <mattias.ellert@physics.uu.se>
++Signed-off-by: Kefu Chai <tchaikov@gmail.com>
++---
++ cmake/modules/SIMDExt.cmake | 17 ++++++++++++++---
++ src/arch/CMakeLists.txt     |  2 +-
++ 2 files changed, 15 insertions(+), 4 deletions(-)
++
++Index: ceph/cmake/modules/SIMDExt.cmake
++===================================================================
++--- ceph.orig/cmake/modules/SIMDExt.cmake
+++++ ceph/cmake/modules/SIMDExt.cmake
++@@ -1,8 +1,13 @@
++ # detect SIMD extensions
++ #
+++# HAVE_ARM
++ # HAVE_ARMV8_CRC
+++# HAVE_ARMV8_CRC_CRYPTO_INTRINSICS
+++# HAVE_ARMV8_CRYPTO
++ # HAVE_ARMV8_SIMD
++ # HAVE_ARM_NEON
+++#
+++# HAVE_INTEL
++ # HAVE_INTEL_SSE
++ # HAVE_INTEL_SSE2
++ # HAVE_INTEL_SSE3
++@@ -11,6 +16,10 @@
++ # HAVE_INTEL_SSE4_1
++ # HAVE_INTEL_SSE4_2
++ #
+++# HAVE_PPC64LE
+++# HAVE_PPC64
+++# HAVE_PPC
+++#
++ # SIMD_COMPILE_FLAGS
++ #
++ 
++@@ -82,14 +91,16 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i
++       endif()
++     endif(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64")
++   endif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|amd64|x86_64|AMD64")
++-elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(powerpc|ppc)64|(powerpc|ppc)64le")
+++elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(powerpc|ppc)")
++   if(CMAKE_SYSTEM_PROCESSOR MATCHES "(powerpc|ppc)64le")
++     set(HAVE_PPC64LE 1)
++     message(STATUS " we are ppc64le")
++-  else()
+++  elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(powerpc|ppc)64")
++     set(HAVE_PPC64 1)
++     message(STATUS " we are ppc64")
++-  endif(CMAKE_SYSTEM_PROCESSOR MATCHES "(powerpc|ppc)64le")
+++  else()
+++    set(HAVE_PPC 1)
+++  endif()
++   CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
++   if(HAS_ALTIVEC)
++     message(STATUS " HAS_ALTIVEC yes")
++Index: ceph/src/arch/CMakeLists.txt
++===================================================================
++--- ceph.orig/src/arch/CMakeLists.txt
+++++ ceph/src/arch/CMakeLists.txt
++@@ -5,7 +5,7 @@ if(HAVE_ARM)
++   list(APPEND arch_srcs arm.c)
++ elseif(HAVE_INTEL)
++   list(APPEND arch_srcs intel.c)
++-elseif(HAVE_POWER8)
+++elseif(HAVE_PPC64LE OR HAVE_PPC64 OR HAVE_PPC)
++   list(APPEND arch_srcs ppc.c)
++ endif()
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e9a450aa585f1c0326592b49d1a87beb2b24074b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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*)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b25aaac1b7682621eba384fd90fa5706671d2599
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,16 @@@
++Index: ceph/src/os/CMakeLists.txt
++===================================================================
++--- ceph.orig/src/os/CMakeLists.txt
+++++ ceph/src/os/CMakeLists.txt
++@@ -98,8 +98,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..297f2de74f21d2637924f1b7230bf8d99c54d7f4
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,18 @@@
++Description: This defines HAVE_REENTRANT_STRSIGNAL as sys_siglist no longer
++  exists with glibc 2.32 and all programs should use strsignal instead.
++Forwarded: no
++Last-Update: 2020-09-21
++
++Index: ceph/CMakeLists.txt
++===================================================================
++--- ceph.orig/CMakeLists.txt
+++++ ceph/CMakeLists.txt
++@@ -497,7 +497,7 @@ if(WITH_THREAD_SAFE_RES_QUERY)
++   set(HAVE_THREAD_SAFE_RES_QUERY 1 CACHE INTERNAL "Thread safe res_query supported.")
++ endif()
++ 
++-option(WITH_REENTRANT_STRSIGNAL "strsignal is reentrant" OFF)
+++option(WITH_REENTRANT_STRSIGNAL "strsignal is reentrant" ON)
++ if(WITH_REENTRANT_STRSIGNAL)
++   set(HAVE_REENTRANT_STRSIGNAL 1 CACHE INTERNAL "Reentrant strsignal is supported.")
++ endif()
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..34658592de4f47422538e7ee33f4756f08594406
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,29 @@@
++Description: include/buffer: include <memory>
++ to address following FTBFS (under GCC 12):
++ .
++ /usr/bin/ccache /usr/bin/clang++-13 -DBOOST_ALL_NO_LIB -DBOOST_ASIO_DISABLE_CONCEPTS -DBOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_T$
++ In file included from /var/ssd/ceph/src/crimson/os/seastore/seastore_types.cc:4:
++ In file included from /var/ssd/ceph/src/crimson/os/seastore/seastore_types.h:14:
++ In file included from /var/ssd/ceph/src/include/denc.h:47:
++ /var/ssd/ceph/src/include/buffer.h:98:37: error: no template named 'unique_ptr' in namespace 'std'; did you mean 'boost::movelib::unique_ptr'?
++ struct unique_leakable_ptr : public std::unique_ptr<T, ceph::nop_delete<T>> {
++                                     ^~~~~~~~~~~~~~~
++                                     boost::movelib::unique_ptr
++ /opt/ceph/include/boost/move/unique_ptr.hpp:354:7: note: 'boost::movelib::unique_ptr' declared here
++ class unique_ptr
++       ^
++Signed-off-by: Kefu Chai <tchaikov@gmail.com>
++Author: Kefu Chai <tchaikov@gmail.com>
++Origin: upstream, https://github.com/ceph/ceph/commit/7c381ba985bd1398ef7d145cc00fae9d0db510e3
++Last-Update: 2022-07-27
++
++--- ceph-16.2.10+ds.orig/src/include/buffer.h
+++++ ceph-16.2.10+ds/src/include/buffer.h
++@@ -41,6 +41,7 @@
++ #include <iosfwd>
++ #include <iomanip>
++ #include <list>
+++#include <memory>
++ #include <vector>
++ #include <string>
++ #if __cplusplus >= 201703L
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..916ff8a790d80575e7b2db063143a6c4adaa4eba
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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)
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9f42077ba2d89705dcf4cda5564b5a1a4f69e513
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,17 @@@
++Description: Fix systemd ceph-osd.target
++ This helps when rebooting.
++Author: Thomas Goirand <zigo@debian.org>
++Forwarded: no
++Last-Update: 2021-01-28
++
++--- ceph-14.2.16.orig/systemd/ceph-osd.target
+++++ ceph-14.2.16/systemd/ceph-osd.target
++@@ -1,7 +1,7 @@
++ [Unit]
++ Description=ceph target allowing to start/stop all ceph-osd@.service instances at once
++ PartOf=ceph.target
++-After=ceph-mon.target
+++After=ceph-mon.target systemd-udev-settle.service
++ Before=ceph.target
++ Wants=ceph.target ceph-mon.target
++ 
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f5a9206b61a7c96b79d7f08f5c93cb0726e8d662
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,17 @@@
++Description: Only yield under ARMv7 and above (#1176)
++Author: Rosen Penev <rosenp@gmail.com>
++Date: Tue, 12 Nov 2019 13:56:53 -0800
++Origin: upstream, https://github.com/facebook/folly/commit/62d8e6e0b91ebd6f878f3066cd9b6e5f3c18a97b.patch
++Last-Update: 2021-11-24
++
++--- ceph-16.2.6+ds.orig/src/rocksdb/third-party/folly/folly/portability/Asm.h
+++++ ceph-16.2.6+ds/src/rocksdb/third-party/folly/folly/portability/Asm.h
++@@ -19,7 +19,7 @@ inline void asm_volatile_pause() {
++   ::_mm_pause();
++ #elif defined(__i386__) || FOLLY_X64
++   asm volatile("pause");
++-#elif FOLLY_AARCH64 || defined(__arm__)
+++#elif FOLLY_AARCH64 || (defined(__arm__) && !(__ARM_ARCH < 7))
++   asm volatile("yield");
++ #elif FOLLY_PPC64
++   asm volatile("or 27,27,27");
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..18b6eed9393c1bfb1aa6c56d24f8e9ab54775579
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,16 @@@
++Description: Link with -pthread instead of -lpthread to fix FTBFS on riscv64
++Forwarded: no
++Last-Update: 2020-03-01
++
++Index: ceph/CMakeLists.txt
++===================================================================
++--- ceph.orig/CMakeLists.txt
+++++ ceph/CMakeLists.txt
++@@ -25,6 +25,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)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..37f0a389f955ecddb4ebc128f84bba485ed9a42f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,22 @@@
++enable-strsignal.patch
++update-java-source-target-flags.patch
++disable-crypto.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
++debian-armel-armhf-buildflags.patch
++fix-bash-completion-location
++32bit-fixes.patch
++add-option-to-disable-ceph-dencoder.patch
++riscv64-link-pthread.patch
++fix-ceph-osd-systemd-target.patch
++compile-ppc.c-on-all-powerpc-machines.patch
++bug1914584.patch
++bug1917414.patch
++cmake-test-for-16-bytes-atomic-support-on-mips-also.patch
++only-yied-under-armv7-and-above.patch
++Fix-build-with-fmt-8-9.patch
++fix-FTBFS-include-memory.h.patch
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6c8b79228618f75f1340fd34038415c55ef0dd53
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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})
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c54eb2ebb23ed8de8240f431a09ec6f18fc2c291
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++ceph python3-ceph
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..274b8b4f7ea18338f556c453808b254e746492f3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/python3*/dist-packages/ceph_argparse.py
++usr/lib/python3*/dist-packages/ceph_daemon.py
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6cdb6ce068a686dc7dbd762d56afa78d7d0ef490
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/python3*/dist-packages/ceph
++usr/lib/python3*/dist-packages/ceph-*.egg-info
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..fd0d214b0a3c5fba39feeed6dd3cc9771730e042
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++python3-ceph: empty-binary-package
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6eb8836707f8ba82952ceaeed78bd6d89c6e6b0f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..98b5d76cbe7dd681299032dea92ced3154d3d810
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/python3*/dist-packages/rados-*.egg-info
++usr/lib/python3*/dist-packages/rados.cpython*.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5f4e6e143e8b0e7fb0c9a28fb22a5607f9842835
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/python3*/dist-packages/rbd-*.egg-info
++usr/lib/python3*/dist-packages/rbd.cpython*.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..57f4559077102ff4c151a9ee416ecf7d897d3a84
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/lib/python3*/dist-packages/rgw-*.egg-info
++usr/lib/python3*/dist-packages/rgw.cpython*.so
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ac8f90ee2aa6de709652d6b4706e4e6399a07d37
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/include/rados/objclass.h
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a2f1849122bc8f9f4b78add2ce6ea91ae71b1731
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++var/lib/ceph/radosgw
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..35805f5e40ed4ee727b9a47703b3900863d5af47
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,9 @@@
++lib/systemd/system/ceph-radosgw*
++usr/bin/radosgw
++usr/bin/radosgw-es
++usr/bin/radosgw-object-expirer
++usr/bin/radosgw-token
++usr/bin/rgw-gap-list
++usr/bin/rgw-gap-list-comparator
++usr/lib/*/libradosgw.so.*
++usr/share/man/man8/radosgw.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5c5e06b8e5f0944863aefe6bb25da543392b18db
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3f1551269529976592678a1c46db7451d15e82a5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0288ab77b3a3ab3e50a4a7b40c171e0416719a57
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7b6b96fe7fa9460893e53ec32eecb932baa70b3a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/bin/rbd-fuse
++usr/share/man/man8/rbd-fuse.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cc617a553047ecb1f13ef96dbca4e955a4a6aeaf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,3 @@@
++lib/systemd/system/ceph-rbd-mirror*
++usr/bin/rbd-mirror
++usr/share/man/man8/rbd-mirror.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..385c4501f770c20de4fcbf079fb4db88e9be5f2a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++usr/bin/rbd-nbd
++usr/share/man/man8/rbd-nbd.8
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8535f20d5a4d385b1c7c5e26ebf8247bf068869d
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++usr/bin/rest-bench
diff --cc debian/rules
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1f851aaa14e8fb05d4fb0a8a947031198531c4c4
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,238 @@@
++#!/usr/bin/make -f
++# -*- makefile -*-
++#export DH_VERBOSE=1
++export DESTDIR=$(CURDIR)/debian/tmp
++
++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 -Os
++export DEB_CXXFLAGS_MAINT_APPEND = -g1 -Os
++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
++# The -Wl,--no-keep-memory should save memory but then will go slower. No choice,
++# as mipsel FTBFS.
++ifeq (32,$(DEB_HOST_ARCH_BITS))
++export DEB_LDFLAGS_MAINT_APPEND= -Wl,--as-needed -latomic -Wl,--no-keep-memory
++endif
++
++# Fix build with fmt 9
++export DEB_CXXFLAGS_MAINT_APPEND += -DFMT_DEPRECATED_OSTREAM
++
++# Enable hardening
++export DEB_BUILD_MAINT_OPTIONS = hardening=+all optimize=-lto
++
++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_PYTHON3=3
++extraopts += -DWITH_CEPHFS_JAVA=ON
++extraopts += -DWITH_CEPHFS_SHELL=ON
++extraopts += -DWITH_TESTS=OFF
++extraopts += -DWITH_SYSTEM_BOOST=ON
++extraopts += -DWITH_SYSTEM_LIBURING=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
++extraopts += -DWITH_RADOSGW_KAFKA_ENDPOINT=OFF
++extraopts += -DCMAKE_BUILD_TYPE=RelWithDebInfo
++extraopts += -DWITH_GRAFANA=ON
++extraopts += -DCMAKE_C_FLAGS_RELWITHDEBINFO="$(CFLAGS)"
++extraopts += -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="$(CXXFLAGS)"
++
++ifneq (,$(findstring $(DEB_HOST_ARCH),amd64 arm64 ppc64el))
++extraopts += -DWITH_RBD_RWL=ON
++extraopts += -DWITH_RBD_SSD_CACHE=ON
++extraopts += -DWITH_SYSTEM_PMDK=ON
++extraopts += -DWITH_BLUESTORE_PMEM=ON
++extraopts += -DWITH_SPDK=ON
++else
++# 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
++endif
++
++# Enable crimson build on supported architectures
++ifneq (,$(findstring $(DEB_HOST_ARCH),amd64 arm64 ppc64el s390x))
++extraopts += -DWITH_SEASTAR=ON -DSeastar_CXX_FLAGS=-DSEASTAR_DEFAULT_ALLOCATOR
++endif
++
++ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
++  NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
++  extraopts += -DBOOST_J=$(NUMJOBS)
++endif
++
++ifneq (,$(filter $(DEB_HOST_ARCH),ia64 m68k ppc64 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 $(MAX_PARALLEL)
++
++override_dh_auto_configure:
++      env | sort
++      dh_auto_configure --buildsystem=cmake -- $(extraopts)
++# 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.
++ifeq ($(DEB_HOST_ARCH),armel)
++      sed 's,^HAVE_ARM_NEON.*,HAVE_ARM_NEON:INTERNAL=0,' -i obj-arm-linux-gnueabi/CMakeCache.txt
++endif
++
++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 440 sudoers.d/ceph-smartctl $(DESTDIR)/etc/sudoers.d/ceph-smartctl
++      install -D -m 755 src/tools/rbd_nbd/rbd-nbd_quiesce $(DESTDIR)/usr/libexec/rbd-nbd/rbd-nbd_quiesce
++
++      install -m 755 src/cephadm/cephadm $(DESTDIR)/usr/sbin/cephadm
++
++      install -m 644 -D monitoring/ceph-mixin/prometheus_alerts.yml $(DESTDIR)/etc/prometheus/ceph/ceph_default_alerts.yml
++
++      # 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 || :
++      # avoid running out of disk space
++      rm -rf $(CURDIR)/obj-*-linux-gnu
++
++# 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
++
++override_dh_installsystemd:
++      # Ensure Debian/Ubuntu specific systemd units are NOT automatically enabled and started
++      # Enable systemd targets only
++      # Start systemd targets only
++      dh_installsystemd --no-stop-on-upgrade --no-restart-after-upgrade -Xceph-mon.service -Xceph-osd.service -X ceph-mds.service
++
++override_dh_strip:
++      dh_strip -pceph-mds --dbg-package=ceph-mds-dbg
++      dh_strip -pceph-fuse
++      dh_strip -pceph-mgr --dbg-package=ceph-mgr-dbg
++      dh_strip -pceph-mon --dbg-package=ceph-mon-dbg
++      dh_strip -pceph-osd --dbg-package=ceph-osd-dbg
++      dh_strip -pceph-base --dbg-package=ceph-base-dbg
++      dh_strip -pcephfs-mirror --dbg-package=cephfs-mirror-dbg
++      dh_strip -prbd-fuse --dbg-package=rbd-fuse-dbg
++      dh_strip -prbd-mirror --dbg-package=rbd-mirror-dbg
++      dh_strip -pceph-immutable-object-cache --dbg-package=ceph-immutable-object-cache-dbg
++      dh_strip -prbd-nbd --dbg-package=rbd-nbd-dbg
++      dh_strip -pceph-common --dbg-package=ceph-common-dbg
++      dh_strip -plibrados2 --dbg-package=librados2-dbg
++      dh_strip -plibsqlite3-mod-ceph --dbg-package=libsqlite3-mod-ceph-dbg
++      dh_strip -plibradosstriper1 --dbg-package=libradosstriper1-dbg
++      dh_strip -plibrbd1 --dbg-package=librbd1-dbg
++      dh_strip -plibcephfs2 --dbg-package=libcephfs2-dbg
++      dh_strip -plibrgw2 --dbg-package=librgw2-dbg
++      dh_strip -pradosgw --dbg-package=radosgw-dbg
++      dh_strip -pceph-test
++
++      # No -dbg packages for python3 bindings.
++      dh_strip -ppython3-cephfs
++      dh_strip -ppython3-rados
++      dh_strip -ppython3-rbd
++      dh_strip -ppython3-rgw
++
++      dh_strip -plibcephfs-jni
++      dh_strip -plibrados-dev
++
++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 ceph ceph-argparse cephfs ceph-common rados rbd rgw; do \
++              dh_python3 -p python3-$$binding --shebang=/usr/bin/python3;      \
++        done
++      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
++      dh_python3 -p cephfs-top --shebang=/usr/bin/python3
++      dh_python3 -p cephadm --shebang=/usr/bin/python3
++
++override_dh_builddeb:
++      dh_builddeb ${skip_packages}
++
++override_dh_gencontrol:
++      dh_gencontrol ${skip_packages}
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6a4dc1a7423167c84e86742936fba91234ea2860
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..163aaf8d82b6c54f23c45f32895dbdfdcc27b047
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,1 @@@
++3.0 (quilt)
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f3cb221b9a69ec86ce991dca016f6138fee4e9a3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d029bb02e6fd46aecf44eb1e883131d459c8f839
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c62999251c0ec07764a08b99b645a1d6e0daf7b6
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5ad6f88230c01b81810171929db7aa0127168008
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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"
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c693a56987c7ee1c8e024a37b75174ce466dc5ad
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..372a057d0ff32bfad286215f612c65cb3d7dceec
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..006dbe63d5701a8af806546d664ff2d90463106f
new file mode 100755 (executable)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,7 @@@
++#!/usr/bin/python3
++
++# Test that rbd and rados can be imported OK
++import rbd
++import rados
++
++print("python-ceph: OK")
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..00feae51fc60e94d66ce9dcb16d12b31a1c04b76
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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 --cc debian/watch
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..899ddb7d7e5edca57981e75496589037a42d4398
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,6 @@@
++version=3
++opts=repacksuffix=+ds,\
++repack,compression=xz,\
++uversionmangle=s/-/~/,\
++dversionmangle=s/\+(debian|dfsg|ds|deb)(\.\d+)?$// \
++ http://download.ceph.com/tarballs/ceph-(\d.*)\.tar\.gz
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..872021328d5d1de5aaee5b01c10750d3a8aa9a63
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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
++