libzfs_sendrecv.c revision 8f38d41910063e19709864b025684a228961299f
/*
* 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
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <libdevinfo.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <stddef.h>
#include <fcntl.h>
#include <stddef.h>
#include <libzfs.h>
#include "zfs_namecheck.h"
#include "zfs_prop.h"
#include "libzfs_impl.h"
#include <fletcher.c> /* XXX */
/*
* Routines for dealing with the AVL tree of fs-nvlists
*/
typedef struct fsavl_node {
char *fn_snapname;
} fsavl_node_t;
static int
{
return (+1);
return (-1);
else
return (0);
}
/*
* Given the GUID of a snapshot, find its containing filesystem and
* (optionally) name.
*/
static nvlist_t *
{
if (fn) {
if (snapname)
}
return (NULL);
}
static avl_tree_t *
{
while ((snapelem =
/*
* Note: if there are multiple snaps with the
* same GUID, we ignore all but one.
*/
else
}
}
return (fsavl);
}
static void
{
void *cookie;
return;
}
/*
* 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) }
*
* "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 void
{
continue;
/* these guys are modifyable, but have no source */
ZPROP_VALUE, &value) == 0);
} else {
char *source;
ZPROP_SOURCE, &source) != 0)
continue;
continue;
}
if (zfs_prop_user(propname) ||
char *value;
ZPROP_VALUE, &value) == 0);
} else {
ZPROP_VALUE, &value) == 0);
}
}
}
static int
{
int rv;
char guidstring[64];
return (-1);
}
/* iterate over props */
/* iterate over snaps, and set sd->parent_fromsnap_guid */
sd->parent_fromsnap_guid = 0;
/* add this fs to nvlist */
/* iterate over children */
return (rv);
}
static int
{
send_data_t sd = { 0 };
int error;
return (EZFS_BADTYPE);
if (avlp)
return (error);
}
/*
* Routines for dealing with the sorted snapshot functionality
*/
typedef struct zfs_node {
} zfs_node_t;
static int
{
return (0);
}
/* ARGSUSED */
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);
}
static 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;
char lastsnap[ZFS_MAXNAMELEN];
int outfd;
/*
* Dumps a backup of the given snapshot (incremental from fromsnap if it's not
* NULL) to the file descriptor specified by outfd.
*/
static int
int outfd)
{
if (fromsnap)
char errbuf[1024];
switch (errno) {
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:
}
}
return (0);
}
static int
{
const char *thissnap;
int err;
return (0);
}
return (0);
}
/* 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) {
}
}
"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 {
char snapname[ZFS_MAXNAMELEN];
}
return (rv);
}
static int
{
char *fsname;
int err;
uint64_t origin_guid = 0;
continue;
if (origin_nv &&
/*
* origin has not been sent yet;
* skip this clone.
*/
continue;
}
if (err)
return (err);
}
if (needagain) {
goto again;
}
return (0);
}
/*
* Dumps a backup of tosnap, incremental from fromsnap if it isn't NULL.
* If 'doall', dump all intermediate snaps.
* If 'replicate', dump special header and do recursively.
*/
int
{
char errbuf[1024];
send_dump_data_t sdd = { 0 };
int err;
"zero-length incremental source"));
}
dmu_replay_record_t drr = { 0 };
zio_cksum_t zc = { 0 };
if (replicate) {
if (fromsnap) {
"fromsnap", fromsnap));
}
if (err)
return (err);
NV_ENCODE_XDR, 0);
if (err) {
}
}
/* write first begin record */
/* write header nvlist */
if (err != -1) {
}
if (err == -1) {
}
/* write end record */
if (err != -1) {
if (err == -1) {
}
}
}
/* dump each stream */
/*
* write final end record. NB: want to do this even if
* there was some error, because it might not be totally
* failed.
*/
dmu_replay_record_t drr = { 0 };
}
}
}
/*
* Routines specific to "zfs recv"
*/
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;
return (ENOMEM);
if (err != 0) {
return (err);
}
if (err != 0) {
"stream (malformed nvlist)"));
return (EINVAL);
}
return (0);
}
static int
{
static int seq;
int err;
return (-1);
return (-1);
if (err)
return (err);
}
if (tryname) {
(void) printf("attempting rename %s to %s\n",
}
} else {
}
seq++;
(void) printf("failed - trying rename %s to %s\n",
}
(void) printf("failed (%u) - "
"will try again on next pass\n", errno);
}
if (err == 0)
(void) printf("success\n");
else
}
if (clp) {
(void) changelist_postfix(clp);
}
return (err);
}
static int
{
int err;
/* unmount it */
return (-1);
if (err) {
return (err);
}
}
if (err != 0) {
}
if (zhp)
if (err == 0)
(void) printf("success\n");
else
}
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 */
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 boolean_t
{
char buf[ZFS_MAXNAMELEN];
if (guid2 == 0)
return (B_FALSE);
if (guid1 == 0)
return (B_TRUE);
return (rv);
}
static int
{
char newname[ZFS_MAXNAMELEN];
int error;
return (0);
return (error);
/*
* Process deletes and renames
*/
uint64_t originguid = 0;
char *fsname, *stream_fsname;
/*
* 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 */
originguid)) {
/* promote it! */
char *origin_fsname;
NULL);
"name", &origin_fsname));
if (error == 0)
}
/*
* list of snapshots is wrong. Need to handle
* them on the next pass.
*/
continue;
}
char *stream_snapname;
/* check for delete */
char name[ZFS_MAXNAMELEN];
continue;
if (error)
else
continue;
}
stream_nvfs = found;
/* check for different snapname */
stream_snapname) != 0) {
char name[ZFS_MAXNAMELEN];
char tryname[ZFS_MAXNAMELEN];
if (error)
else
}
}
/* check for delete */
if (stream_nvfs == NULL) {
continue;
if (error)
else
continue;
}
(void) printf("local fs %s does not have fromsnap "
"(%s in stream); must have been deleted locally; "
continue;
}
"name", &stream_fsname));
"parentfromsnap", &stream_parent_fromsnap_guid));
/* check for rename */
if ((stream_parent_fromsnap_guid != 0 &&
char tryname[ZFS_MAXNAMELEN];
/*
* 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);
}
}
if (error)
else
}
}
/* do another pass to fix up temporary names */
(void) printf("another pass:\n");
goto again;
}
return (needagain);
}
static int
{
char tofs[ZFS_MAXNAMELEN];
char errbuf[1024];
int error;
"cannot receive"));
"can not specify snapshot name for multi-snapshot stream"));
}
/*
* Read in the nvlist from the stream.
*/
if (drr->drr_payloadlen != 0) {
"must use -d to receive replication "
"(send -R) stream"));
}
if (error) {
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));
"/@");
/* zfs_receive_one() will create_parents() */
}
}
}
/* 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 (anyerr)
error = -1;
return (error);
}
static int
{
/* XXX would be great to use lseek if possible... */
if (byteswap)
case DRR_BEGIN:
/* NB: not to be used on v2 stream packages */
break;
case DRR_END:
return (0);
case DRR_OBJECT:
if (byteswap) {
}
break;
case DRR_WRITE:
if (byteswap) {
}
break;
case DRR_FREEOBJECTS:
case DRR_FREE:
break;
default:
assert(!"invalid record type");
}
}
return (-1);
}
/*
* Restores a backup of tosnap from the file descriptor specified by infd.
*/
static int
{
char *cp;
char errbuf[1024];
char chopprefix[ZFS_MAXNAMELEN];
uint64_t parent_snapguid = 0;
"cannot receive"));
if (stream_avl != NULL) {
if (err)
if (flags.canmountoff) {
}
return (-1);
if (err)
}
/*
* 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.
*/
/*
* They specified a fs with -d, we want to tack on
* everything but the pool name stored in the stream
*/
"argument - snapshot not allowed with -d"));
}
*cp = '\0';
/*
* If they specified a filesystem without -d, we want to
* tack on everything after the fs specified in the
* first name from the stream.
*/
*cp = '\0';
}
/*
* Determine name of 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 &&
char suffix[ZFS_MAXNAMELEN];
}
}
} else {
/*
* if the fs does not exist, look for it based on the
* fromsnap GUID
*/
"cannot receive incremental stream"));
char snap[ZFS_MAXNAMELEN];
}
}
}
/*
* 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(clp) != 0) {
return (-1);
}
}
return (-1);
}
}
} else {
/*
* Destination FS does not exist. Therefore we better
* be creating a new filesystem (either from a full
* backup, or a clone)
*/
if (!stream_wantsnewfs) {
}
/* Do the recvbackup ioctl to the fs's parent. */
if (err != 0) {
}
}
}
(void) printf("%s %s stream of %s into %s\n",
}
ioctl_errno = errno;
/*
* 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; "
}
}
}
*cp = '@';
}
if (ioctl_err != 0) {
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;
default:
}
}
/*
* Mount or recreate the /dev links for the target filesystem
* (if created, or if we tore them down to do an incremental
* restore), and the /dev links for the new snapshot (if
* created). Also mount any children of the target filesystem
* if we did an incremental receive.
*/
zfs_handle_t *h;
*cp = '\0';
*cp = '@';
if (h) {
if (h->zfs_type == ZFS_TYPE_VOLUME) {
} else if (newfs) {
}
zfs_close(h);
}
}
if (clp) {
}
return (-1);
char buf1[64];
char buf2[64];
if (delta == 0)
delta = 1;
}
return (0);
}
/*
* Restores a backup of tosnap from the file descriptor specified by infd.
*/
int
{
int err;
char errbuf[1024];
zio_cksum_t zcksum = { 0 };
"cannot receive"));
"(%s) does not exist"), tosnap);
}
/* 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)"));
}
"stream (bad snapshot name)"));
}
} else {
"stream is unsupported version %llu"),
drrb->drr_version);
}
}