{"action":"created","comment":{"url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/comments/36122139","id":36122139,"diff_hunk":"@@ -0,0 +1,659 @@\n+# Copyright 2015 ClusterHQ. See LICENSE file for details.\n+\n+\"\"\"\n+Python wrappers for libzfs_core interfaces.\n+\n+As a rule, there is a Python function for each C function.\n+The signatures of the Python functions generally follow those of the\n+functions, but the argument types are natural to Python.\n+nvlists are wrapped as dictionaries or lists depending on their usage.\n+Some parameters have default values depending on typical use for\n+increased convenience.  Output parameters are not used and return values\n+are directly returned.  Error conditions are signalled by exceptions\n+rather than by integer error codes.\n+\"\"\"\n+\n+import errno\n+import threading\n+from . import exceptions\n+from . import _error_translation as xlate\n+from .bindings import libzfs_core\n+from ._constants import MAXNAMELEN\n+from ._nvlist import nvlist_in, nvlist_out\n+\n+\n+def lzc_create(name, is_zvol = False, props = {}):\n+    '''\n+    Create a ZFS filesystem or a ZFS volume (\"zvol\").\n+\n+    :param bytes name: a name of the dataset to be created.\n+    :param bool is_zvol: whether to create a zvol (false by default).\n+    :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).\n+    :type props: dict of bytes:Any\n+\n+    :raises FilesystemExists: if a dataset with the given name already exists.\n+    :raises ParentNotFound: if a parent dataset of the requested dataset does not exist.\n+    :raises PropertyInvalid: if one or more of the specified properties is invalid\n+                             or has an invalid type or value.\n+\n+    Wraps ``int lzc_create(const char *fsname, dmu_objset_type_t type, nvlist_t *props)``.\n+    '''\n+    if is_zvol:\n+        ds_type = _lib.DMU_OST_ZVOL\n+    else:\n+        ds_type = _lib.DMU_OST_ZFS\n+    with nvlist_in(props) as nvlist:\n+        ret = _lib.lzc_create(name, ds_type, nvlist)\n+    xlate.lzc_create_xlate_error(ret, name, is_zvol, props)\n+\n+\n+def lzc_clone(name, origin, props = {}):\n+    '''\n+    Clone a ZFS filesystem or a ZFS volume (\"zvol\") from a given snapshot.\n+\n+    :param bytes name: a name of the dataset to be created.\n+    :param bytes origin: a name of the origin snapshot.\n+    :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).\n+    :type props: dict of bytes:Any\n+\n+    :raises FilesystemExists: if a dataset with the given name already exists.\n+    :raises DatasetNotFound: if either a parent dataset of the requested dataset\n+                             or the origin snapshot does not exist.\n+    :raises PropertyInvalid: if one or more of the specified properties is invalid\n+                             or has an invalid type or value.\n+\n+    .. note::\n+        Because of a deficiency of the underlying C interface\n+        :exc:`.DatasetNotFound` can mean that either a parent filesystem of the target\n+        or the origin snapshot does not exist.\n+        It is currently impossible to distinguish between the cases.\n+        :func:`lzc_hold` can be used to check that the snapshot exists and ensure that\n+        it is not destroyed before cloning.\n+    '''\n+    with nvlist_in(props) as nvlist:\n+        ret = _lib.lzc_clone(name, origin, nvlist)\n+    xlate.lzc_clone_xlate_error(ret, name, origin, props)\n+\n+\n+def lzc_rollback(name):\n+    '''\n+    Roll back a filesystem or volume to its most recent snapshot.\n+\n+    :param bytes name: a name of the dataset to be rolled back.\n+    :return: a name of the most recent snapshot.\n+    :rtype: bytes\n+\n+    :raises FilesystemNotFound: if the dataset does not exist.\n+    :raises SnapshotNotFound: if the dataset does not have any snapshots.\n+    :raises NameInvalid: if the dataset name is invalid.\n+    :raises NameTooLong: if the dataset name is too long.\n+    '''\n+    # Account for terminating NUL in C strings.\n+    snapnamep = _ffi.new('char[]', MAXNAMELEN + 1)\n+    ret = _lib.lzc_rollback(name, snapnamep, MAXNAMELEN + 1)\n+    xlate.lzc_rollback_xlate_error(ret, name)\n+    return _ffi.string(snapnamep)\n+\n+\n+def lzc_snapshot(snaps, props = {}):\n+    '''\n+    Create snapshots.\n+\n+    All snapshots must be in the same pool.\n+\n+    Optionally snapshot properties can be set on all snapshots.\n+    Currently  only user properties (prefixed with \"user:\") are supported.\n+\n+    Either all snapshots are successfully created or none are created if\n+    an exception is raised.\n+\n+    :param snaps: a list of names of snapshots to be created.\n+    :type snaps: list of bytes\n+    :param props: a `dict` of ZFS dataset property name-value pairs (empty by default).\n+    :type props: dict of bytes:bytes\n+\n+    :raises SnapshotFailure: if one or more snapshots could not be created.\n+\n+    .. note::\n+        :exc:`.SnapshotFailure` is a compound exception that provides at least\n+        one detailed error object in :attr:`SnapshotFailure.errors` `list`.\n+\n+    .. warning::\n+        The underlying implementation reports an individual, per-snapshot error\n+        only for :exc:`.SnapshotExists` condition and *sometimes* for\n+        :exc:`.NameTooLong`.\n+        In all other cases a single error is reported without connection to any\n+        specific snapshot name(s).\n+\n+        This has the following implications:\n+\n+        * if multiple error conditions are encountered only one of them is reported\n+\n+        * unless only one snapshot is requested then it is impossible to tell\n+          how many snapshots are problematic and what they are\n+\n+        * only if there are no other error conditions :exc:`.SnapshotExists`\n+          is reported for all affected snapshots\n+\n+        * :exc:`.NameTooLong` can behave either in the same way as\n+          :exc:`.SnapshotExists` or as all other exceptions.\n+          The former is the case where the full snapshot name exceeds the maximum\n+          allowed length but the short snapshot name (after '@') is within\n+          the limit.\n+          The latter is the case when the short name alone exceeds the maximum\n+          allowed length.\n+    '''\n+    snaps_dict = { name: None for name in snaps }\n+    errlist = {}\n+    with nvlist_in(snaps_dict) as snaps_nvlist, nvlist_in(props) as props_nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_snapshot(snaps_nvlist, props_nvlist, errlist_nvlist)\n+    xlate.lzc_snapshot_xlate_errors(ret, errlist, snaps, props)\n+\n+\n+lzc_snap = lzc_snapshot\n+\n+\n+def lzc_destroy_snaps(snaps, defer):\n+    '''\n+    Destroy snapshots.\n+\n+    They must all be in the same pool.\n+    Snapshots that do not exist will be silently ignored.\n+\n+    If 'defer' is not set, and a snapshot has user holds or clones, the\n+    destroy operation will fail and none of the snapshots will be\n+    destroyed.\n+\n+    If 'defer' is set, and a snapshot has user holds or clones, it will be\n+    marked for deferred destruction, and will be destroyed when the last hold\n+    or clone is removed/destroyed.\n+\n+    The operation succeeds if all snapshots were destroyed (or marked for\n+    later destruction if 'defer' is set) or didn't exist to begin with.\n+\n+    :param snaps: a list of names of snapshots to be destroyed.\n+    :type snaps: list of bytes\n+    :param bool defer: whether to mark busy snapshots for deferred destruction\n+                       rather than immediately failing.\n+\n+    :raises SnapshotDestructionFailure: if one or more snapshots could not be created.\n+\n+    .. note::\n+        :exc:`.SnapshotDestructionFailure` is a compound exception that provides at least\n+        one detailed error object in :attr:`SnapshotDestructionFailure.errors` `list`.\n+\n+        Typical error is :exc:`SnapshotIsCloned` if `defer` is `False`.\n+        The snapshot names are validated quite loosely and invalid names are typically\n+        ignored as nonexisiting snapshots.\n+\n+        A snapshot name referring to a filesystem that doesn't exist is ignored.\n+        However, non-existent pool name causes :exc:`PoolNotFound`.\n+    '''\n+    snaps_dict = { name: None for name in snaps }\n+    errlist = {}\n+    with nvlist_in(snaps_dict) as snaps_nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_destroy_snaps(snaps_nvlist, defer, errlist_nvlist)\n+    xlate.lzc_destroy_snaps_xlate_errors(ret, errlist, snaps, defer)\n+\n+\n+def lzc_bookmark(bookmarks):\n+    '''\n+    Create bookmarks.\n+\n+    :param bookmarks: a dict that maps names of wanted bookmarks to names of existing snapshots.\n+    :type bookmarks: dict of bytes to bytes\n+\n+    :raises BookmarkFailure: if any of the bookmarks can not be created for any reason.\n+\n+    The bookmarks `dict` maps from name of the bookmark (e.g. :file:`{pool}/{fs}#{bmark}`) to\n+    the name of the snapshot (e.g. :file:`{pool}/{fs}@{snap}`).  All the bookmarks and\n+    snapshots must be in the same pool.\n+    '''\n+    errlist = {}\n+    with nvlist_in(bookmarks) as nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_bookmark(nvlist, errlist_nvlist)\n+    xlate.lzc_bookmark_xlate_errors(ret, errlist, bookmarks)\n+\n+\n+def lzc_get_bookmarks(fsname, props = []):\n+    '''\n+    Retrieve a list of bookmarks for the given file system.\n+\n+    :param bytes fsname: a name of the filesystem.\n+    :param props: a `list` of properties that will be returned for each bookmark.\n+    :type props: list of bytes\n+    :return: a `dict` that maps the bookmarks' short names to their properties.\n+    :rtype: dict of bytes:dict\n+\n+    :raises FilesystemNotFound: if the filesystem is not found.\n+\n+    The following are valid properties on bookmarks:\n+\n+    guid : integer\n+        globally unique identifier of the snapshot the bookmark refers to\n+    createtxg : integer\n+        txg when the snapshot the bookmark refers to was created\n+    creation : integer\n+        timestamp when the snapshot the bookmark refers to was created\n+\n+    Any other properties passed in ``props`` are ignored without reporting\n+    any error.\n+    Values in the returned dictionary map the names of the requested properties\n+    to their respective values.\n+    '''\n+    bmarks = {}\n+    props_dict = { name: None for name in props }\n+    with nvlist_in(props_dict) as nvlist:\n+        with nvlist_out(bmarks) as bmarks_nvlist:\n+            ret = _lib.lzc_get_bookmarks(fsname, nvlist, bmarks_nvlist)\n+    xlate.lzc_get_bookmarks_xlate_error(ret, fsname, props)\n+    return bmarks\n+\n+\n+def lzc_destroy_bookmarks(bookmarks):\n+    '''\n+    Destroy bookmarks.\n+\n+    :param bookmarks: a list of the bookmarks to be destroyed.\n+                      The bookmarks are specified as :file:`{fs}#{bmark}`.\n+    :type bookmarks: list of bytes\n+\n+    :raises BookmarkDestructionFailure: if any of the bookmarks may not be destroyed.\n+\n+    The bookmarks must all be in the same pool.\n+    Bookmarks that do not exist will be silently ignored.\n+    This also includes the case where the filesystem component of the bookmark\n+    name does not exist.\n+    However, an invalid bookmark name will cause :exc:`.NameInvalid` error\n+    reported in :attr:`SnapshotDestructionFailure.errors`.\n+\n+    Either all bookmarks that existed are destroyed or an exception is raised.\n+    '''\n+    errlist = {}\n+    bmarks_dict = { name: None for name in bookmarks }\n+    with nvlist_in(bmarks_dict) as nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_destroy_bookmarks(nvlist, errlist_nvlist)\n+    xlate.lzc_destroy_bookmarks_xlate_errors(ret, errlist, bookmarks)\n+\n+\n+def lzc_snaprange_space(firstsnap, lastsnap):\n+    '''\n+    Calculate a size of data referenced by snapshots in the inclusive range between\n+    the ``firstsnap`` and the ``lastsnap`` and not shared with any other datasets.\n+\n+    :param bytes firstsnap: the name of the first snapshot in the range.\n+    :param bytes lastsnap: the name of the last snapshot in the range.\n+    :return: the calculated stream size, in bytes.\n+    :rtype: `int` or `long`\n+\n+    :raises SnapshotNotFound: if either of the snapshots does not exist.\n+    :raises NameInvalid: if the name of either snapshot is invalid.\n+    :raises NameTooLong: if the name of either snapshot is too long.\n+    :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.\n+    :raises PoolsDiffer: if the snapshots belong to different pools.\n+\n+    ``lzc_snaprange_space`` calculates total size of blocks that exist\n+    because they are referenced only by one or more snapshots in the given range\n+    but no other dataset.\n+    In other words, this is the set of blocks that were born after the snap before\n+    firstsnap, and died before the snap after the last snap.\n+    Yet another interpretation is that the result of ``lzc_snaprange_space`` is the size\n+    of the space that would be freed if the snapshots in the range are destroyed.\n+\n+    If the same snapshot is given as both the ``firstsnap`` and the ``lastsnap``.\n+    In that case ``lzc_snaprange_space`` calculates space used by the snapshot.\n+    '''\n+    valp = _ffi.new('uint64_t *')\n+    ret = _lib.lzc_snaprange_space(firstsnap, lastsnap, valp)\n+    xlate.lzc_snaprange_space_xlate_error(ret, firstsnap, lastsnap)\n+    return int(valp[0])\n+\n+\n+def lzc_hold(holds, fd = None):\n+    '''\n+    Create *user holds* on snapshots.  If there is a hold on a snapshot,\n+    the snapshot can not be destroyed.  (However, it can be marked for deletion\n+    by :func:`lzc_destroy_snaps` ( ``defer`` = `True` ).)\n+\n+    :param holds: the dictionary of names of the snapshots to hold mapped to the hold names.\n+    :type holds: dict of bytes : bytes\n+    :type fd: int or None\n+    :param fd: if not None then it must be the result of :func:`os.open` called as ``os.open(\"/dev/zfs\", O_EXCL)``.\n+    :type fd: int or None\n+    :return: a list of the snapshots that do not exist.\n+    :rtype: list of bytes\n+\n+    :raises HoldFailure: if a hold was impossible on one or more of the snapshots.\n+    :raises BadHoldCleanupFD: if ``fd`` is not a valid file descriptor associated with :file:`/dev/zfs`.\n+\n+    The snapshots must all be in the same pool.\n+\n+    If ``fd`` is not None, then when the ``fd`` is closed (including on process\n+    termination), the holds will be released.  If the system is shut down\n+    uncleanly, the holds will be released when the pool is next opened\n+    or imported.\n+\n+    Holds for snapshots which don't exist will be skipped and have an entry\n+    added to the return value, but will not cause an overall failure.\n+    No exceptions is raised if all holds, for snapshots that existed, were succesfully created.\n+    Otherwise :exc:`.HoldFailure` exception is raised and no holds will be created.\n+    :attr:`.HoldFailure.errors` may contain a single element for an error that is not\n+    specific to any hold / snapshot, or it may contain one or more elements\n+    detailing specific error per each affected hold.\n+    '''\n+    errlist = {}\n+    if fd is None:\n+        fd = -1\n+    with nvlist_in(holds) as nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_hold(nvlist, fd, errlist_nvlist)\n+    xlate.lzc_hold_xlate_errors(ret, errlist, holds, fd)\n+    # If there is no error (no exception raised by _handleErrList), but errlist\n+    # is not empty, then it contains missing snapshots.\n+    assert all(x == errno.ENOENT for x in errlist.itervalues())\n+    return errlist.keys()\n+\n+\n+def lzc_release(holds):\n+    '''\n+    Release *user holds* on snapshots.\n+\n+    If the snapshot has been marked for\n+    deferred destroy (by lzc_destroy_snaps(defer=B_TRUE)), it does not have\n+    any clones, and all the user holds are removed, then the snapshot will be\n+    destroyed.\n+\n+    The snapshots must all be in the same pool.\n+\n+    :param holds: a ``dict`` where keys are snapshot names and values are\n+                  lists of hold tags to remove.\n+    :type holds: dict of bytes : list of bytes\n+    :return: a list of any snapshots that do not exist and of any tags that do not\n+             exist for existing snapshots.\n+             Such tags are qualified with a corresponding snapshot name\n+             using the following format :file:`{pool}/{fs}@{snap}#{tag}`\n+    :rtype: list of bytes\n+\n+    :raises HoldReleaseFailure: if one or more existing holds could not be released.\n+\n+    Holds which failed to release because they didn't exist will have an entry\n+    added to errlist, but will not cause an overall failure.\n+\n+    This call is success if ``holds`` was empty or all holds that\n+    existed, were successfully removed.\n+    Otherwise an exception will be raised.\n+    '''\n+    errlist = {}\n+    holds_dict = {}\n+    for snap, hold_list in holds.iteritems():\n+        if not isinstance(hold_list, list):\n+            raise TypeError('holds must be in a list')\n+        holds_dict[snap] = {hold: None for hold in hold_list}\n+    #holds_dict = {snap: {hold: None for hold in hold_list}\n+    #                for snap, hold_list in holds.iteritems()}\n+    with nvlist_in(holds_dict) as nvlist:\n+        with nvlist_out(errlist) as errlist_nvlist:\n+            ret = _lib.lzc_release(nvlist, errlist_nvlist)\n+    xlate.lzc_release_xlate_errors(ret, errlist, holds)\n+    # If there is no error (no exception raised by _handleErrList), but errlist\n+    # is not empty, then it contains missing snapshots and tags.\n+    assert all(x == errno.ENOENT for x in errlist.itervalues())\n+    return errlist.keys()\n+\n+\n+def lzc_get_holds(snapname):\n+    '''\n+    Retrieve list of *user holds* on the specified snapshot.\n+\n+    :param bytes snapname: the name of the snapshot.\n+    :return: holds on the snapshot along with their creation times\n+             in seconds since the epoch\n+    :rtype: dict of bytes : int\n+    '''\n+    holds = {}\n+    with nvlist_out(holds) as nvlist:\n+        ret = _lib.lzc_get_holds(snapname, nvlist)\n+    xlate.lzc_get_holds_xlate_error(ret, snapname)\n+    return holds\n+\n+\n+def lzc_send(snapname, fromsnap, fd, flags = []):\n+    '''\n+    Generate a zfs send stream for the specified snapshot and write it to\n+    the specified file descriptor.\n+\n+    :param bytes snapname: the name of the snapshot to send.\n+    :param fromsnap: if not None the name of the starting snapshot\n+                     for the incremental stream.\n+    :type fromsnap: bytes or None\n+    :param int fd: the file descriptor to write the send stream to.\n+    :param flags: the flags that control what enhanced features can be used\n+                  in the stream.\n+    :type flags: list of bytes\n+\n+    :raises SnapshotNotFound: if either the starting snapshot is not `None` and does not exist,\n+                              or if the ending snapshot does not exist.\n+    :raises NameInvalid: if the name of either snapshot is invalid.\n+    :raises NameTooLong: if the name of either snapshot is too long.\n+    :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.\n+    :raises PoolsDiffer: if the snapshots belong to different pools.\n+    :raises IOError: if an input / output error occurs while writing to ``fd``.\n+    :raises ValueError: if the ``flags`` contain an invalid flag name.\n+\n+    If ``fromsnap`` is None, a full (non-incremental) stream will be sent.\n+    If ``fromsnap`` is not None, it must be the full name of a snapshot or\n+    bookmark to send an incremental from, e.g. :file:`{pool}/{fs}@{earlier_snap}`\n+    or :file:`{pool}/{fs}#{earlier_bmark}`.\n+\n+    The specified snapshot or bookmark must represent an earlier point in the history\n+    of ``snapname``.\n+    It can be an earlier snapshot in the same filesystem or zvol as ``snapname``,\n+    or it can be the origin of ``snapname``'s filesystem, or an earlier\n+    snapshot in the origin, etc.\n+    ``fromsnap`` must be strictly an earlier snapshot, specifying the same snapshot\n+    as both ``fromsnap`` and ``snapname`` is an error.\n+\n+    If ``flags`` contains *\"large_blocks\"*, the stream is permitted\n+    to contain ``DRR_WRITE`` records with ``drr_length`` > 128K, and ``DRR_OBJECT``\n+    records with ``drr_blksz`` > 128K.\n+\n+    If ``flags`` contains *\"embedded_data\"*, the stream is permitted\n+    to contain ``DRR_WRITE_EMBEDDED`` records with\n+    ``drr_etype`` == ``BP_EMBEDDED_TYPE_DATA``,\n+    which the receiving system must support (as indicated by support\n+    for the *embedded_data* feature).\n+\n+    .. note::\n+        ``lzc_send`` can actually accept a filesystem name as the ``snapname``.\n+        In that case ``lzc_send`` acts as if a temporary snapshot was created\n+        after the start of the call and before the stream starts being produced.\n+    '''\n+    if fromsnap is not None:\n+        c_fromsnap = fromsnap\n+    else:\n+        c_fromsnap = _ffi.NULL\n+    c_flags = 0\n+    for flag in flags:\n+        c_flag = {\n+            'embedded_data':    _lib.LZC_SEND_FLAG_EMBED_DATA,\n+            'large_blocks':     _lib.LZC_SEND_FLAG_LARGE_BLOCK,\n+        }.get(flag)\n+        if c_flag is None:\n+            raise ValueError('Unknown flag value ' + flag)\n+        c_flags |= c_flag\n+\n+    ret = _lib.lzc_send(snapname, c_fromsnap, fd, c_flags)\n+    xlate.lzc_send_xlate_error(ret, snapname, fromsnap, fd, flags)\n+\n+\n+def lzc_send_space(snapname, fromsnap = None):\n+    '''\n+    Estimate size of a full or incremental backup stream\n+    given the optional starting snapshot and the ending snapshot.\n+\n+    :param bytes snapname: the name of the snapshot for which the estimate should be done.\n+    :param fromsnap: the optional starting snapshot name.\n+                     If not `None` then an incremental stream size is estimated,\n+                     otherwise a full stream is esimated.\n+    :type fromsnap: `bytes` or `None`\n+    :return: the estimated stream size, in bytes.\n+    :rtype: `int` or `long`\n+\n+    :raises SnapshotNotFound: if either the starting snapshot is not `None` and does not exist,\n+                              or if the ending snapshot does not exist.\n+    :raises NameInvalid: if the name of either snapshot is invalid.\n+    :raises NameTooLong: if the name of either snapshot is too long.\n+    :raises SnapshotMismatch: if ``fromsnap`` is not an ancestor snapshot of ``snapname``.\n+    :raises PoolsDiffer: if the snapshots belong to different pools.\n+\n+    ``fromsnap``, if not ``None``,  must be strictly an earlier snapshot,\n+    specifying the same snapshot as both ``fromsnap`` and ``snapname`` is an error.\n+    '''\n+    if fromsnap is not None:\n+        c_fromsnap = fromsnap\n+    else:\n+        c_fromsnap = _ffi.NULL\n+    valp = _ffi.new('uint64_t *')\n+    ret = _lib.lzc_send_space(snapname, c_fromsnap, valp)\n+    xlate.lzc_send_space_xlate_error(ret, snapname, fromsnap)\n+    return int(valp[0])\n+\n+\n+def lzc_receive(snapname, fd, force = False, origin = None, props = {}):\n+    '''\n+    Receive from the specified ``fd``, creating the specified snapshot.\n+\n+    :param bytes snapname: the name of the snapshot to create.\n+    :param int fd: the file descriptor from which to read the stream.\n+    :param bool force: whether to roll back or destroy the target filesystem\n+                       if that is required to receive the stream.\n+    :param origin: the optional origin snapshot name if the stream is for a clone.\n+    :type origin: bytes or None\n+    :param props: the properties to set on the snapshot as *received* properties.\n+    :type props: dict of bytes : Any\n+\n+    :raises IOError: if an input / output error occurs while reading from the ``fd``.\n+    :raises DatasetExists: if the snapshot named ``snapname`` already exists.\n+    :raises DatasetExists: if the stream is a full stream and the destination filesystem already exists.\n+    :raises DatasetExists: if ``force`` is `True` but the destination filesystem could not\n+                           be rolled back to a matching snapshot because a newer snapshot\n+                           exists and it is an origin of a cloned filesystem.\n+    :raises StreamMismatch: if an incremental stream is received and the latest\n+                            snapshot of the destination filesystem does not match\n+                            the source snapshot of the stream.\n+    :raises StreamMismatch: if a full stream is received and the destination\n+                            filesystem already exists and it has at least one snapshot,\n+                            and ``force`` is `False`.\n+    :raises StreamMismatch: if an incremental clone stream is received but the specified\n+                            ``origin`` is not the actual received origin.\n+    :raises DestinationModified: if an incremental stream is received and the destination\n+                                 filesystem has been modified since the last snapshot\n+                                 and ``force`` is `False`.\n+    :raises DestinationModified: if a full stream is received and the destination\n+                                 filesystem already exists and it does not have any\n+                                 snapshots, and ``force`` is `False`.\n+    :raises DatasetNotFound: if the destination filesystem and its parent do not exist.\n+    :raises DatasetNotFound: if the ``origin`` is not `None` and does not exists.","path":"libzfs_core/_libzfs_core.py","position":560,"original_position":560,"commit_id":"164da56b65be80f995a937404df730a6cbbf21ec","original_commit_id":"164da56b65be80f995a937404df730a6cbbf21ec","user":{"login":"exarkun","id":254565,"avatar_url":"https://avatars.githubusercontent.com/u/254565?v=3","gravatar_id":"","url":"https://api.github.com/users/exarkun","html_url":"https://github.com/exarkun","followers_url":"https://api.github.com/users/exarkun/followers","following_url":"https://api.github.com/users/exarkun/following{/other_user}","gists_url":"https://api.github.com/users/exarkun/gists{/gist_id}","starred_url":"https://api.github.com/users/exarkun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/exarkun/subscriptions","organizations_url":"https://api.github.com/users/exarkun/orgs","repos_url":"https://api.github.com/users/exarkun/repos","events_url":"https://api.github.com/users/exarkun/events{/privacy}","received_events_url":"https://api.github.com/users/exarkun/received_events","type":"User","site_admin":false},"body":"typo, `exists`","created_at":"2015-08-03T19:36:24Z","updated_at":"2015-08-03T19:36:24Z","html_url":"https://github.com/ClusterHQ/pyzfs/pull/1#discussion_r36122139","pull_request_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/comments/36122139"},"html":{"href":"https://github.com/ClusterHQ/pyzfs/pull/1#discussion_r36122139"},"pull_request":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1","id":39670291,"html_url":"https://github.com/ClusterHQ/pyzfs/pull/1","diff_url":"https://github.com/ClusterHQ/pyzfs/pull/1.diff","patch_url":"https://github.com/ClusterHQ/pyzfs/pull/1.patch","issue_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/1","number":1,"state":"open","locked":false,"title":"pyzfs: a thin libzfs_core wrapper","user":{"login":"avg-I","id":1499700,"avatar_url":"https://avatars.githubusercontent.com/u/1499700?v=3","gravatar_id":"","url":"https://api.github.com/users/avg-I","html_url":"https://github.com/avg-I","followers_url":"https://api.github.com/users/avg-I/followers","following_url":"https://api.github.com/users/avg-I/following{/other_user}","gists_url":"https://api.github.com/users/avg-I/gists{/gist_id}","starred_url":"https://api.github.com/users/avg-I/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/avg-I/subscriptions","organizations_url":"https://api.github.com/users/avg-I/orgs","repos_url":"https://api.github.com/users/avg-I/repos","events_url":"https://api.github.com/users/avg-I/events{/privacy}","received_events_url":"https://api.github.com/users/avg-I/received_events","type":"User","site_admin":false},"body":"This pull request covers wrappers for committed libzfs_core API.\r\nAnother pull request (or maybe more than one) is coming for the libzfs_core extensions required by flocker.\r\n\r\nIssues:\r\n* https://clusterhq.atlassian.net/browse/FLOC-787\r\n* https://clusterhq.atlassian.net/browse/FLOC-782\r\n* https://clusterhq.atlassian.net/browse/FLOC-781\r\n* https://clusterhq.atlassian.net/browse/FLOC-780\r\n* https://clusterhq.atlassian.net/browse/FLOC-779\r\n* https://clusterhq.atlassian.net/browse/FLOC-776","created_at":"2015-07-10T10:12:04Z","updated_at":"2015-08-03T19:36:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9a920cb4e3ccb96ce17c4a67b4a6d0b54cb7f9fb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1/commits","review_comments_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1/comments","review_comment_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/1/comments","statuses_url":"https://api.github.com/repos/ClusterHQ/pyzfs/statuses/164da56b65be80f995a937404df730a6cbbf21ec","head":{"label":"ClusterHQ:pyzfs-0.1","ref":"pyzfs-0.1","sha":"164da56b65be80f995a937404df730a6cbbf21ec","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":32338081,"name":"pyzfs","full_name":"ClusterHQ/pyzfs","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/pyzfs","description":"Python bindings to the stable ZFS ABI","fork":false,"url":"https://api.github.com/repos/ClusterHQ/pyzfs","forks_url":"https://api.github.com/repos/ClusterHQ/pyzfs/forks","keys_url":"https://api.github.com/repos/ClusterHQ/pyzfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/pyzfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/pyzfs/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/pyzfs/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/pyzfs/events","assignees_url":"https://api.github.com/repos/ClusterHQ/pyzfs/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/pyzfs/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/pyzfs/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/pyzfs/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/pyzfs/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/pyzfs/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/pyzfs/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/pyzfs/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/pyzfs/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/pyzfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/pyzfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/pyzfs/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/pyzfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/pyzfs/merges","archive_url":"https://api.github.com/repos/ClusterHQ/pyzfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/pyzfs/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/pyzfs/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/pyzfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/pyzfs/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/pyzfs/releases{/id}","created_at":"2015-03-16T16:28:40Z","updated_at":"2015-07-29T19:40:54Z","pushed_at":"2015-07-19T11:29:35Z","git_url":"git://github.com/ClusterHQ/pyzfs.git","ssh_url":"git@github.com:ClusterHQ/pyzfs.git","clone_url":"https://github.com/ClusterHQ/pyzfs.git","svn_url":"https://github.com/ClusterHQ/pyzfs","homepage":null,"size":636,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"ClusterHQ:master","ref":"master","sha":"324dc980b7eadeb4d3490f76c37a987a52897d3a","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":32338081,"name":"pyzfs","full_name":"ClusterHQ/pyzfs","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/pyzfs","description":"Python bindings to the stable ZFS ABI","fork":false,"url":"https://api.github.com/repos/ClusterHQ/pyzfs","forks_url":"https://api.github.com/repos/ClusterHQ/pyzfs/forks","keys_url":"https://api.github.com/repos/ClusterHQ/pyzfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/pyzfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/pyzfs/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/pyzfs/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/pyzfs/events","assignees_url":"https://api.github.com/repos/ClusterHQ/pyzfs/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/pyzfs/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/pyzfs/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/pyzfs/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/pyzfs/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/pyzfs/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/pyzfs/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/pyzfs/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/pyzfs/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/pyzfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/pyzfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/pyzfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/pyzfs/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/pyzfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/pyzfs/merges","archive_url":"https://api.github.com/repos/ClusterHQ/pyzfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/pyzfs/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/pyzfs/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/pyzfs/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/pyzfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/pyzfs/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/pyzfs/releases{/id}","created_at":"2015-03-16T16:28:40Z","updated_at":"2015-07-29T19:40:54Z","pushed_at":"2015-07-19T11:29:35Z","git_url":"git://github.com/ClusterHQ/pyzfs.git","ssh_url":"git@github.com:ClusterHQ/pyzfs.git","clone_url":"https://github.com/ClusterHQ/pyzfs.git","svn_url":"https://github.com/ClusterHQ/pyzfs","homepage":null,"size":636,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1"},"html":{"href":"https://github.com/ClusterHQ/pyzfs/pull/1"},"issue":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/1"},"comments":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/ClusterHQ/pyzfs/statuses/164da56b65be80f995a937404df730a6cbbf21ec"}}}}