/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <stddef.h>
#include <fcntl.h>
#include <pthread.h>
#include <umem.h>
#include <libzfs.h>
#include "zfs_namecheck.h"
#include "zfs_prop.h"
#include "zfs_share.h"
#include "zfs_fletcher.h"
#include "libzfs_impl.h"
#include <sha2.h>
#include <sys/zio_checksum.h>
/* in libzfs_dataset.c */
const char *, int, char *);
struct recv_data;
const char *, nvlist_t *, avl_tree_t *);
typedef struct dedup_arg {
int inputfd;
int outputfd;
} dedup_arg_t;
typedef struct dataref {
} dataref_t;
typedef struct dedup_entry {
typedef struct dedup_table {
int numhashbits;
static int
{
int count;
n >>= 1;
return (count);
}
static size_t
{
return (0);
return (outlen);
}
static void
{
"Dedup table full. Deduplication will continue "
"with existing table entries"));
}
return;
}
!= NULL) {
}
}
/*
* Using the specified dedup table, do a lookup for an entry with
* the checksum cs. If found, return the block's reference info
* in *dr. Otherwise, insert a new entry in the dedup table, using
* the reference information specified by *dr.
*
* return value: true - entry was found
* false - entry was not found
*/
static boolean_t
{
return (B_TRUE);
}
}
return (B_FALSE);
}
static int
{
}
/*
* This function is started in a separate thread when the dedup option
* has been requested. The main send thread determines the list of
* snapshots to be included in the send stream and makes the ioctl calls
* for each one. But instead of having the ioctl send the output to the
* the output fd specified by the caller of zfs_send()), the
* ioctl is told to direct the output to a pipe, which is read by the
* alternate thread running THIS function. This function does the
* dedup'ing by:
* 1. building a dedup table (the DDT)
* 2. doing checksums on each data block and inserting a record in the DDT
* 3. looking for matching checksums, and
* 4. sending a DRR_WRITE_BYREF record instead of a write record whenever
* a duplicate block is found.
* The output of this function then goes to the output fd requested
* by the caller of zfs_send().
*/
static void *
{
int outfd;
/*
* numbuckets must be a power of 2. Increase number to
* a power of 2 if necessary.
*/
if (!ISP2(numbuckets))
/* Initialize the write-by-reference block. */
wbr_drr.drr_payloadlen = 0;
case DRR_BEGIN:
{
int fflags;
ZIO_SET_CHECKSUM(&stream_cksum, 0, 0, 0, 0);
/* set the DEDUP feature flag for this stream */
goto out;
}
perror("fread");
outfd) == -1)
goto out;
}
break;
}
case DRR_END:
{
/* use the recalculated checksum */
sizeof (dmu_replay_record_t))) == -1)
goto out;
break;
}
case DRR_OBJECT:
{
goto out;
if (drro->drr_bonuslen > 0) {
ofp);
if (cksum_and_write(buf,
goto out;
}
break;
}
case DRR_SPILL:
{
goto out;
goto out;
break;
}
case DRR_FREEOBJECTS:
{
goto out;
break;
}
case DRR_WRITE:
{
/*
* Use the existing checksum if it's dedup-capable,
* else calculate a SHA256 checksum for it.
*/
zero_cksum) ||
SHA256Init(&ctx);
}
&dataref)) {
/* block already present in stream */
if (cksum_and_write(&wbr_drr,
sizeof (dmu_replay_record_t), &stream_cksum,
outfd) == -1)
goto out;
} else {
/* block not previously seen */
if (cksum_and_write(drr,
sizeof (dmu_replay_record_t), &stream_cksum,
outfd) == -1)
goto out;
if (cksum_and_write(buf,
goto out;
}
break;
}
case DRR_FREE:
{
goto out;
break;
}
default:
/* should never happen, so assert */
}
}
out:
return (NULL);
}
/*
* Routines for dealing with the AVL tree of fs-nvlists
*/
typedef struct fsavl_node {
char *fn_name;
} fsavl_node_t;
static int
{
/* sort by type */
return (+1);
return (-1);
/* sort by guid */
return (+1);
return (-1);
else
return (0);
}
/*
* Given the GUID of a snapshot or share, find its containing filesystem and
* (optionally) name.
*/
static nvlist_t *
{
}
return (NULL);
}
static void
{
void *cookie;
return;
}
/*
* Given an nvlist, produce an avl tree of snapshots and shares,
* ordered by guid.
*/
static avl_tree_t *
{
return (NULL);
}
return (NULL);
}
/*
* Note: if there are multiple snaps with the
* same GUID, we ignore all but one.
*/
else
if (type == ZFS_TYPE_SNAPSHOT) {
}
if (type == ZFS_TYPE_SHARE) {
}
}
}
return (fsavl);
}
/*
* Routines for dealing with the giant nvlist of fs-nvlists, etc.
*/
typedef struct send_data {
const char *fromsnap;
const char *tosnap;
/*
* The header nvlist is of the following format:
* {
* "tosnap" -> string
* "fromsnap" -> string (if incremental)
* "fss" -> {
* id -> {
*
* "name" -> string (full name; for debugging)
* "parentfromsnap" -> number (guid of fromsnap in parent)
*
* "props" -> { name -> value (only if set here) }
* "snaps" -> { name (lastname) -> number (guid) }
* "snapprops" -> { name (lastname) -> { name -> value } }
* "shares" -> { name (lastname) -> number (guid) }
* "shareprops" -> { name (lastname) -> { name -> value } }
*
* "origin" -> number (guid) (if clone)
* "sent" -> boolean (not on-disk)
* }
* }
* }
*
*/
} send_data_t;
static int
{
char *snapname;
/*
* NB: if there is no fromsnap here (it's a newly created fs in
* an incremental replication), we will substitute the tosnap.
*/
}
return (0);
}
static int
{
char *sharename;
/* don't send the automatic share */
if (zfs_is_auto_share(zhp)) {
return (0);
}
/* add creation time and guid */
return (0);
}
static void
{
int spa_version;
/*
* 'zfs send -b' sends received rather than effective property values.
* It is an error to send received properties before pool version 22,
* and we assume that has already been checked.
*/
/*
* An internal "$hasrecvd" property tracks whether a dataset was
* received on or after pool version 22 (received properties). If that
* internal property is not found, 'zfs send -b' treats the dataset as
* if it was sent without -b. This behavior is consistent in the sense
* that 'send -b' always sends "original" properties: In the replication
* chain A->B->C, the initial replication A->B is a special case in
* which the original properties are the effective and not the received
* properties. This behavior potentially frees backup applications from
* deciding when to use -b, since they can use -b by default and get the
* desired behavior (sending the original properties of A) in both the
* A->B and B->C cases.
*/
ZPROP_PROPERTY, &zpd) == 0) {
}
if (zfs_prop_user(propname)) {
} else {
/*
* Realistically, this should never happen. However,
* we want the ability to add DSL properties without
* needing to make incompatible version changes. We
* need to ignore unknown properties to allow older
* software to still send datasets containing these
* properties, with the unknown properties elided.
*/
continue;
/*
* Encryption is setonce so readonly will return
* true, but we need it in the stream to make
* the set of stream props encryption,checksum,keysource
* all consistent.
*/
continue;
/*
* If sending a filesystem that has already been
* converted to zfs-shares, we omit the SHARENFS
* and SHARESMB properties. Otherwise they can
* trigger inappropriate sharing on the target.
*/
if ((prop == ZFS_PROP_SHARENFS ||
prop == ZFS_PROP_SHARESMB) &&
continue;
}
prop == ZFS_PROP_REFQUOTA ||
prop == ZFS_PROP_REFRESERVATION) {
char *source;
ZPROP_VALUE, &value) == 0);
continue;
/*
* May have no source before SPA_VERSION_RECVD_PROPS,
* but is still modifiable.
*/
ZPROP_SOURCE, &source) == 0) {
ZPROP_SOURCE_VAL_RECVD) != 0))
continue;
}
} else {
char *source;
ZPROP_SOURCE, &source) != 0)
continue;
continue;
}
char *value;
ZPROP_VALUE, &value) == 0);
} else {
ZPROP_VALUE, &value) == 0);
}
}
}
/*
* recursively generate nvlists describing datasets. See comment
* for the data structure send_data_t above for description of contents
* of the nvlist.
*/
static int
{
int rv = 0;
return (-1);
}
/* iterate over props */
/* iterate over snaps, and set sd->parent_fromsnap_guid */
sd->parent_fromsnap_guid = 0;
/* iterate over shares */
/* add this fs to nvlist */
/* iterate over children */
return (rv);
}
static int
{
int error;
return (EZFS_BADTYPE);
return (error);
}
return (EZFS_NOMEM);
}
return (0);
}
/*
* Routines for dealing with the sorted snapshot functionality
*/
typedef struct zfs_node {
} zfs_node_t;
static int
{
if (node) {
/*
* If this snapshot was renamed while we were creating the
* AVL tree, it's possible that we already inserted it under
* its old name. Remove the old handle before adding the new
* one.
*/
}
return (0);
}
static int
{
/*
* Sort them according to creation time. We use the hidden
* CREATETXG property to get an absolute ordering of snapshots.
*/
return (-1);
return (+1);
else
return (0);
}
int
{
int ret = 0;
avl_destroy(&avl);
return (ret);
}
/*
* Routines specific to "zfs send"
*/
typedef struct send_dump_data {
/* these are all just the short snapname (the part after the @) */
const char *fromsnap;
const char *tosnap;
int outfd;
void *filter_cb_arg;
int cleanup_fd;
/*
* Dumps a backup of the given snapshot (incremental from fromsnap if it's not
* NULL) to the file descriptor specified by outfd.
*/
static int
{
"fromsnap", fromsnap));
}
if (debugnv) {
}
switch (errno) {
case ENOKEY:
/* Treat this as a warning rather than an error */
return (0);
case EXDEV:
"not an earlier snapshot from the same fs"));
case ENOENT:
"incremental source (@%s) does not exist"),
}
case EDQUOT:
case EFBIG:
case EIO:
case ENOLINK:
case ENOSPC:
case ENOSTR:
case ENXIO:
case EPIPE:
case ERANGE:
case EFAULT:
case EROFS:
default:
}
}
if (debugnv)
return (0);
}
static int
{
int error = 0;
char *thissnap;
/*
* zfs_send() only opens a cleanup_fd for sends that need it,
* e.g. replication and doall.
*/
return (0);
/*
* It's OK if the parent no longer exists. The send code will
* handle that error.
*/
if (pzhp) {
}
return (error);
}
static int
{
char *thissnap;
int err;
if (err == 0) {
err = 0;
}
return (err);
}
return (0);
}
if (istosnap)
char *snapname;
/*
* Filter out all intermediate snapshots except origin
* snapshots needed to replicate clones.
*/
&snapname);
"snapprops", &snapprops));
} else {
}
}
/*
* If a filter function exists, call it to determine whether
* this snapshot will be sent.
*/
/*
* This snapshot is filtered out. Don't send it, and don't
* set prevsnap_obj, so it will be as if this snapshot didn't
* exist, and the next accepted snapshot will be sent as
* an incremental from the last accepted one, or as the
* first (and full) snapshot in the case of a replication,
* non-incremental send.
*/
return (0);
}
/*
* If this is the first snapshot for this file system or volume and a
* recursive or replication stream is being generated, be sure that any
* missing origins are accounted for. If the origin is missing and it
* is a self contained stream, force a full stream rather than stopping
* at the origin. Otherwise, a missing origin causes a warning to be
* issued.
*/
} else {
char *p;
*p = '\0';
"not included in stream\n", ds);
}
}
}
if (err) {
err = 0;
return (err);
}
/* send it */
}
return (err);
}
static int
{
int rv = 0;
"could not send %s@%s: does not exist\n",
return (0);
}
/*
* If this fs does not have fromsnap, and we're doing
* recursive, we need to send a full stream from the
* beginning (or an incremental from the origin if this
* is a clone). If we're doing non-recursive, then let
* them get the error.
*/
ZFS_IOC_OBJSET_STATS, &zc) != 0) {
}
}
sdd->prevsnap_obj = 0;
"WARNING: could not send %s@%s:\n"
"incremental source (%s@%s) does not exist\n",
"WARNING: could not send %s@%s:\n"
"incremental source (%s@%s) "
"is not earlier than it\n",
} else {
"could not send %s@%s: does not exist\n",
}
}
return (rv);
}
static int
{
/* Mark the clone origin snapshots. */
char *snapname;
if (origin_guid == 0)
continue;
origin_guid, &snapname);
"missing_clone_origin"));
continue;
}
/*
* If the origin doesn't have the snapshot specified in the
* send dump data, the origin will not be included in the
* stream. In such a case, tag the origin's parent with
* missing_clone_origin. Otherwise, tag the origin snapshot
* with is_clone_origin.
*/
0) {
"missing_clone_origin"));
} else {
&snapprops));
&snapprops));
"is_clone_origin"));
}
}
char *fsname;
int err;
continue;
if (origin_guid != 0) {
"sent") == ENOENT) {
/*
* origin has not been sent yet;
* skip this clone.
*/
continue;
}
}
return (-1);
if (err)
return (err);
}
if (needagain) {
goto again;
}
return (0);
}
/*
* Generate a send stream for the dataset identified by the argument zhp.
*
* The content of the send stream is the snapshot identified by
* 'tosnap'. Incremental streams are requested in two ways:
* - from the snapshot identified by "fromsnap" (if non-null) or
* - from the origin of the dataset identified by zhp, which must
* be a clone. In this case, "fromsnap" is null and "fromorigin"
* is TRUE.
*
* The send stream is recursive (i.e. dumps a hierarchy of snapshots) and
* uses a special header (with a hdrtype field of DMU_COMPOUNDSTREAM)
* if "replicate" is set. If "doall" is set, dump all the intermediate
* snapshots. The DMU_COMPOUNDSTREAM header is used in the "doall"
* case too. If "props" is set, send properties.
*/
int
{
int err;
int spa_version;
int featureflags = 0;
"zero-length incremental source"));
}
"cannot read the pool version"));
}
"sending received properties is not supported on "
"pool version %d"), spa_version);
}
if (version >= ZPL_VERSION_SA) {
}
if (crypt != ZIO_CRYPT_OFF) {
}
}
if (spa_version >= SPA_VERSION_USERREFS &&
errbuf));
}
}
}
if (fromsnap) {
"fromsnap", fromsnap));
}
"not_recursive"));
}
if (err)
goto err_out;
NV_ENCODE_XDR, 0);
if (debugnvp)
else
if (err) {
goto stderr_out;
}
}
/* write first begin record */
/* write header nvlist */
}
if (err == -1) {
goto stderr_out;
}
/* write end record */
if (err != -1) {
if (err == -1) {
goto stderr_out;
}
}
}
/* dump each stream */
else
if (debugnvp)
if (holdsnaps) {
++holdseq;
if (sdd.cleanup_fd < 0) {
goto stderr_out;
}
} else {
}
}
}
/*
* write final end record. NB: want to do this even if
* there was some error, because it might not be totally
* failed.
*/
}
}
(void) pthread_cancel(tid);
}
return (err);
}
/*
* Routines specific to "zfs recv"
*/
typedef struct recv_data {
const char *tosnap;
int infd;
char **top_zfs;
int cleanup_fd;
void *recv_cb_arg;
} recv_data_t;
static int
{
int rv;
do {
} while (rv > 0);
"failed to read from stream"));
"cannot receive")));
}
if (zc) {
if (byteswap)
else
}
return (0);
}
static int
{
char *buf;
int err;
if (err != 0) {
return (err);
}
if (err != 0) {
"stream (malformed nvlist)"));
return (EINVAL);
}
return (0);
}
/* ARGSUSED */
static int
{
int err;
return (err);
}
static int
{
static int seq;
int err;
return (-1);
/*
* ZFS_PROP_NAME is overloaded and is a little too aggressive here.
* All we need to do is unmount children that may be sharing the same
* mountpoint (and therefore need their resource updated).
*/
return (-1);
if (err) {
return (err);
}
if (tryname) {
return (-1);
zfs_share_remove_one(zhp) != 0) {
return (-1);
}
(void) printf("attempting rename %s to %s\n",
}
if (err == 0)
} else {
}
seq++;
(void) printf("failed - trying rename %s to %s\n",
}
if (err == 0)
(void) printf("failed (%u) - "
"will try again on next pass\n", errno);
}
if (err == 0)
(void) printf("success\n");
else
}
(void) changelist_postfix(clp);
return (err);
}
static int
{
int err = 0;
int spa_version;
return (-1);
return (-1);
}
return (-1);
zfs_share_remove_one(zhp) != 0) {
return (-1);
}
if (err == 0) {
(void) printf("success\n");
} else {
}
/*
* Deferred destroy might destroy the snapshot or only mark it to be
* destroyed later, and it returns success in either case.
*/
ZFS_TYPE_SNAPSHOT))) {
}
return (err);
}
typedef struct guid_to_name_data {
char *name;
static int
{
int err;
return (EEXIST);
}
return (err);
}
static int
char *name)
{
/* exhaustive search all local snapshots or shares */
int err = 0;
char *cp;
return (0);
}
}
if (cp)
*cp = '\0';
if (cp)
*cp = '/';
if (zhp) {
}
}
/*
* Return true if dataset guid1 is created before guid2.
*/
static int
{
int rv;
if (guid2 == 0)
return (0);
if (guid1 == 0)
return (1);
return (-1);
return (-1);
}
return (rv);
}
static int
{
char *fromsnap;
int error;
ENOENT);
return (0);
return (error);
/*
* Process deletes and renames
*/
/*
* First find the stream's fs, so we can check for
* a different origin (due to "zfs promote")
*/
if (stream_nvfs != NULL)
break;
}
/* check for promote */
case 1: {
/* promote it! */
char *origin_fsname;
/*
* We had the wrong origin, therefore our
* list of snapshots is wrong. Handle
* them on the next pass.
*/
if (origin_nvfs == NULL ||
"name", &origin_fsname)) {
"cannot receive: %s because its "
"origin isn't in the stream\n"),
fsname);
continue;
}
if (error == 0) {
(void) printf("promoting %s\n",
fsname);
} else {
"promote of %s failed\n"), fsname);
}
continue;
}
/*
* No promote therefore no need for another pass.
*/
default:
continue;
case -1:
return (-1);
}
}
char *stream_snapname;
/* check for delete */
continue;
continue;
if (error)
else
continue;
}
stream_nvfs = found;
stream_snapname, &props)) {
props) == 0) {
ZFS_IOC_SET_PROP, &zc);
}
}
/* check for different snapname */
stream_snapname) != 0) {
if (error)
else
}
}
char *stream_sharename;
/* check for delete */
if (error)
else
continue;
}
stream_nvfs = found;
if (0 == nvlist_lookup_nvlist(stream_nvfs,
"shareprops", &props) &&
0 == nvlist_lookup_nvlist(props,
stream_sharename, &props)) {
/* must not modify stream_nv */
(void) nvlist_remove(shprops,
(void) nvlist_remove(shprops,
shprops) == 0) {
ZFS_IOC_SET_PROP, &zc);
}
}
/* check for different sharename */
stream_sharename) != 0) {
if (error)
else
}
}
/* check for delete */
if (stream_nvfs == NULL) {
continue;
if (error)
else
continue;
}
if (fromguid == 0) {
(void) printf("local fs %s does not have "
"fromsnap (%s in stream); must have "
"been deleted locally; ignoring\n",
}
continue;
}
"name", &stream_fsname));
"parentfromsnap", &stream_parent_fromsnap_guid));
/*
* Check for rename. If the exact receive path is specified, it
* does not count as a rename, but we still need to check the
* datasets beneath it.
*/
if ((stream_parent_fromsnap_guid != 0 &&
parent_fromsnap_guid != 0 &&
/*
* NB: parent might not be found if we used the
* tosnap for stream_parent_fromsnap_guid,
* because the parent is a newly-created fs;
* we'll be able to rename it after we recv the
* new fs.
*/
char *pname;
&pname));
} else {
tryname[0] = '\0';
(void) printf("local fs %s new parent "
"not found\n", fsname);
}
}
newname[0] = '\0';
newname));
}
if (error)
else
}
}
/* do another pass to fix up temporary names */
(void) printf("another pass:\n");
goto again;
}
return (needagain);
}
static int
{
char *cp;
int error;
"cannot receive"));
/*
* Read in the nvlist from the stream.
*/
if (drr->drr_payloadlen != 0) {
if (error) {
goto out;
}
}
ENOENT);
"cannot specify snapshot name for multi-snapshot stream"));
goto out;
}
/*
* Read in the end record and verify checksum.
*/
goto out;
}
goto out;
}
"incorrect header checksum"));
goto out;
}
if (drr->drr_payloadlen != 0) {
&stream_fss));
"couldn't allocate avl tree"));
goto out;
}
int i;
i = 0;
} else {
}
} else {
}
/* zfs_receive_one() will create_parents() */
}
NV_UNIQUE_NAME, 0));
}
/* Unmount renamed filesystems before receiving. */
CL_GATHER_NOMOUNT : 0), 0);
softerr |=
}
}
}
}
}
/*
* Get the fs specified by the first path in the stream (the top level
* specified by 'zfs send') and pass it to each invocation of
* zfs_receive_one().
*/
*cp = '\0';
/* Finally, receive each contained stream */
do {
/*
* we should figure out if it has a recoverable
* error, in which case do a recv_skip() and drive on.
* Note, if we fail due to already having this guid,
* zfs_receive_one() will take care of it (ie,
* recv_skip() and return 0).
*/
error = 0;
break;
}
} while (error == 0);
/*
* Now that we have the fs's they sent us, try the
* renames again.
*/
}
out:
if (stream_nv)
if (softerr)
error = -2;
if (anyerr)
error = -1;
return (error);
}
static void
{
}
static void
zfs_prop_errlist_trunc_msg(int n)
{
if (n == 1) {
"1 more property could not be set\n"));
} else {
"%d more properties could not be set\n"), n);
}
}
static int
{
"cannot receive:"));
/* XXX would be great to use lseek if possible... */
if (byteswap)
case DRR_BEGIN:
/* NB: not to be used on v2 stream packages */
if (drr->drr_payloadlen != 0) {
"invalid substream header"));
}
break;
case DRR_END:
return (0);
case DRR_OBJECT:
if (byteswap) {
}
break;
case DRR_WRITE:
if (byteswap) {
}
break;
case DRR_SPILL:
if (byteswap) {
}
break;
case DRR_WRITE_BYREF:
case DRR_FREEOBJECTS:
case DRR_FREE:
break;
default:
"invalid record type"));
}
}
return (-1);
}
/*
* Tell zfs_props_predict() how we will update the named property. This adds an
* entry to prop_updates with the input needed to predict the value of the named
* property.
*/
static void
{
char *valstr;
/*
* A pair with no value (DATA_TYPE_BOOLEAN) indicates that we're
* preserving the current effective value in spite of what we
* receive. Otherwise, if a value was specified, it will
* override the received value.
*/
switch (nvpair_type(pair)) {
case DATA_TYPE_BOOLEAN:
break;
case DATA_TYPE_STRING:
}
break;
case DATA_TYPE_UINT64:
}
break;
}
}
/*
* Property settings specified on the command line override received
* values, so what we received makes no difference to the prediction if
* we already recorded a local setting.
*/
void *value;
int err;
&valstr);
} else {
&intval);
}
source |= ZPROP_SRC_NONE;
}
}
}
/*
* If receiving the incremental send stream will change the effective
* mountpoint, we want to unmount the dataset before the receive so we can mount
* it at the new mountpoint after the receive. If the mountpoint changes after
* we make the prediction and before we receive, the worst that can happen is
* that we fail to remount (leaving the dataset mounted at the previous
* mountpoint) or we remount unnecessarily (updating atime).
* Return B_TRUE in *changed if mountpoint or property related to mountpoint
* changes, else B_FALSE on no change.
*/
static int
{
int err;
/*
* If we're not receiving properties, then the receive cannot change the
* mountpoint.
*/
return (0);
/*
* Tell dsl_props_predict() how we will update the mountpoint and
* canmount properties.
*/
/* Predict the new effective property values. */
if (err != 0)
return (err);
/* Read the prediction. */
}
}
/*
* Compare the predicted effective value with the current effective
* value.
*
* We don't use zfs_is_mounted() to get the current mountpoint from the
* mount table because even if the dataset is unmounted, there might
* still be a mounted child dataset that inherits a new mountpoint and
* needs to be remounted. We let changelist_gather() worry about which
* datasets are actually mounted. Here we only worry about changes in
* the effective values of the mountpoint and canmount properties.
*/
/*
* The effective mountpoint is changing or we need to unmount
* the dataset.
*/
}
return (0);
}
/*
* Returns B_TRUE if dsname names a descendant of the specified top level
* dataset, ignoring snapshot suffixes.
*/
static boolean_t
{
const char *end;
}
/*
* Restores a backup of tosnap from the file descriptor specified by infd.
*/
static int
{
const char *chopprefix;
"cannot receive"));
ENOENT);
/*
* Determine how much of the snapshot name stored in the stream
* we are going to tack on to the name they specified on the
* command line, and how much we are going to chop off.
*
* If they specified a snapshot, chop the entire name stored in
* the stream.
*/
/*
* A filesystem was specified with -e. We want to tack on only
* the tail of the sent snapshot path.
*/
"argument - snapshot not allowed with -e"));
}
if (chopprefix == NULL) {
/*
* The tail is the poolname, so we need to
* prepend a path separator.
*/
cp[0] = '/';
chopprefix = cp;
} else {
}
/*
* A filesystem was specified with -d. We want to tack on
* everything but the first element of the sent snapshot path
* (all but the pool name).
*/
"argument - snapshot not allowed with -d"));
}
if (chopprefix == NULL)
/*
* If a filesystem was specified without -d or -e, we want to
* tack on everything after the fs specified by 'zfs send'.
*/
} else {
/* A snapshot was specified as an exact path (no -d or -e). */
if (recursive) {
"cannot specify snapshot name for multi-snapshot "
"stream"));
}
}
chopprefix[0] == '\0');
/*
* Determine the name of the top level dataset specified by 'zfs
* receive', store in zc_top_ds.
*/
/*
* The top level received dataset includes the first path
* component after the chopped prefix.
*/
/*
* Append the next component to zc_top_ds without
* including the trailing slash or snapshot delimiter.
* If end == NULL, leave that to zfs_name_valid().
*/
end - chopprefix);
}
}
/*
* Determine the name of the destination snapshot, store in zc_value.
*/
}
/*
* Determine the name of the origin snapshot, store in zc_string.
*/
"local origin for clone %s does not exist"),
}
}
if (stream_wantsnewfs) {
/*
* if the parent fs does not exist, look for it based on
* the parent snap GUID
*/
"cannot receive new filesystem stream"));
if (cp)
*cp = '\0';
if (cp &&
}
}
} else {
/*
* if the fs does not exist, look for it based on the
* fromsnap GUID
*/
"cannot receive incremental stream"));
/*
* If the exact receive path was specified and this is the
* topmost path in the stream, then if the fs does not exist we
* should look no further.
*/
}
}
}
/*
* Destination fs exists. Therefore this should either
* be an incremental, or the stream specifies a new fs
* (full stream or clone) and they want us to blow it
* away (and have therefore specified -F and removed any
* snapshots).
*/
if (stream_wantsnewfs) {
"destination '%s' exists\n"
"must specify -F to overwrite it"),
}
&zc) == 0) {
"destination has snapshots (eg. %s)\n"
"must destroy them to overwrite it"),
}
}
return (-1);
}
if (stream_wantsnewfs &&
"destination '%s' is a clone\n"
"must destroy it to overwrite it"),
}
/* We can't do online recv in this case */
return (-1);
}
if (changelist_prefix(name_clp) != 0) {
return (-1);
}
}
/*
* See if this is a recursive stream and we are
* receiving a descendant dataset.
*/
&changed) != 0) {
goto error_out;
}
if (changed) {
CL_GATHER_NOMOUNT : 0;
gflags, 0);
if (mountpoint_clp != NULL &&
changelist_prefix(mountpoint_clp) != 0) {
}
}
}
} else {
/*
* Destination filesystem does not exist. Therefore we better
* be creating a new filesystem (either from a full backup, or
* a clone). It would therefore be invalid if the user
* specified only the pool name (i.e. if the destination name
* contained no slash character).
*/
if (!stream_wantsnewfs ||
}
/*
* Trim off the final dataset component so we perform the
* recvbackup ioctl to the filesystems's parent.
*/
*cp = '\0';
}
/* validate parents exist */
return (-1);
}
}
int props_err = 0;
if (recursive) {
/*
* If we're doing it recursively, then ignore properties
* that are not valid for this type of dataset.
*/
0));
if (prop == ZPROP_INVAL ||
VERIFY(0 == nvlist_add_nvpair(
applicable, pair));
}
}
props = applicable;
}
/* _valid_proplist() returns a newly allocated list */
goto error_out;
if (!nvlist_empty(props))
if (props_err != 0)
goto error_out;
}
if (stream_avl != NULL) {
char *snapname;
int ret;
if (err) {
NV_UNIQUE_NAME, 0));
}
}
recvdprops, &zc) != 0) {
goto error_out;
}
snapname, &snapprops_nvlist));
}
if (ret != 0)
goto error_out;
} else {
goto error_out;
}
}
(void) printf("%s %s stream of %s into %s\n",
}
}
ioctl_errno = errno;
if (err == 0) {
}
zc.zc_nvlist_dst_size = 0;
if (err == 0 && snapprops_nvlist) {
}
}
sizeof (fsname)));
const char *sharename;
zfs_handle_t *h;
int sce;
/*
* See if the share already exists.
*/
if (h != NULL) {
zfs_close(h);
continue;
}
if (sce != 0) {
"error receiving %s\n",
zfs_name);
}
continue;
}
/*
* Can't share until the received datasets are
* mounted.
*/
"create %s\n", zfs_name);
}
if (sce == 0) {
zfs_name) == 0);
}
}
}
}
/*
* It may be that this snapshot already exists,
* in which case we want to consume & ignore it
* rather than failing.
*/
/*
* XXX Do this faster by just iterating over snaps in
* this fs. Also if zc_value does not exist, we will
* get a strange "does not exist" error message.
*/
*cp = '\0';
*cp = '@';
(void) printf("snap %s already exists; "
}
if (err == 0)
}
}
*cp = '@';
}
switch (ioctl_errno) {
case ENODEV:
*cp = '\0';
"most recent snapshot of %s does not\n"
*cp = '@';
break;
case ETXTBSY:
"destination %s has been modified\n"
break;
case EEXIST:
if (newfs) {
/* it's the containing fs that exists */
*cp = '\0';
}
"destination already exists"));
*cp = '@';
break;
case EINVAL:
break;
case ECKSUM:
"invalid stream (checksum mismatch)"));
break;
case ENOTSUP:
"pool must be upgraded to receive this stream."));
break;
case EDQUOT:
break;
default:
}
}
/*
* Mount the target filesystem (if created). Also mount any
* children of the target filesystem if we did a replication
* receive (indicated by stream_avl being non-NULL).
*/
zfs_handle_t *h;
*cp = '\0';
if (h != NULL) {
if (h->zfs_type == ZFS_TYPE_VOLUME) {
*cp = '@';
} else if (newfs || stream_avl) {
/*
* for mounting and sharing later.
*/
}
zfs_close(h);
}
*cp = '@';
}
}
if (mountpoint_clp != NULL) {
}
if (prop_errflags & ZPROP_ERR_NOCLEAR) {
"failed to clear unreceived properties on %s"),
}
if (prop_errflags & ZPROP_ERR_NORESTORE) {
"failed to restore original properties on %s"),
}
return (-1);
if (delta == 0)
delta = 1;
}
return (0);
return (-1);
}
static int
{
"cannot override received %s"), propname);
return (EINVAL);
}
}
return (0);
}
static int
{
int err;
int hdrtype;
"cannot receive"));
}
/* read in the BEGIN record */
&zcksum)))
return (err);
/* It's the double end record at the end of a package */
return (ENODATA);
}
/* the kernel needs the non-byteswapped begin record */
drr_noswap = drr;
/*
* We computed the checksum in the wrong byteorder in
* recv_read() above; do it again correctly.
*/
}
"stream (bad magic number)"));
}
if (!DMU_STREAM_SUPPORTED(featureflags) ||
"stream has unsupported feature, feature flags = 0x%llx"),
}
"stream (bad snapshot name)"));
}
/*
* We were not called from zfs_receive_package(). Get
* the fs specified by 'zfs send'.
*/
char *cp;
*cp = '\0';
}
stream_nv, stream_avl));
} else {
}
}
/*
* Restores a backup of tosnap from the file descriptor specified by infd.
* Return 0 on total success, -2 if some things couldn't be
* (-1 will override -2).
*/
int
void *recv_cb_arg)
{
int err;
int cleanup_fd;
VERIFY(cleanup_fd >= 0);
/* mount and share received datasets */
}
}
err = -1;
} else {
/*
* The following code is not needed when top_zfs is
* non-null because the changelist code above already
* includes all defined shares. If any of them should
* be triggered to come alive, it will find them and
* activate them.
*/
(void) zfs_share_one(sh_zhp);
} else {
(void) zfs_share_one_quiet(
sh_zhp);
}
}
}
}
}
if (top_zfs)
return (err);
}