Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for property overrides (-o|-x) in zfs receive #5349

Merged
merged 1 commit into from
May 9, 2017

Conversation

loli10K
Copy link
Contributor

@loli10K loli10K commented Oct 29, 2016

Description

This allow users to specify "-o property=value" to override and "-x property" to exclude properties when receiving a stream, which is useful when using zfs send/receive for periodic backup/replication because it allows to change properties such as canmount, mountpoint, or compression without modifying the source dataset.

root@debian-8-zfs:~# # main pool needs performance: disable compression
root@debian-8-zfs:~# zfs set compression=off srcpool
root@debian-8-zfs:~# zfs create srcpool/fs1
root@debian-8-zfs:~# zfs create srcpool/vol1
root@debian-8-zfs:~# zfs get -o all compression -r srcpool
NAME          PROPERTY     VALUE     RECEIVED  SOURCE
srcpool       compression  off       -         local
srcpool/fs1   compression  off       -         inherited from srcpool
srcpool/vol1  compression  off       -         inherited from srcpool
root@debian-8-zfs:~# zfs snap -r srcpool@snap1
root@debian-8-zfs:~# zfs send -R srcpool@snap1 > $TMPDIR/repl.dat
root@debian-8-zfs:~# # backup doesn't need performance, favor space instead
root@debian-8-zfs:~# zfs recv -F -o compression=gzip-9 dstpool < $TMPDIR/repl.dat
root@debian-8-zfs:~# zfs get -o all compression -r dstpool
NAME                PROPERTY     VALUE     RECEIVED  SOURCE
dstpool             compression  gzip-9    off       local
dstpool@snap1       compression  -         -         -
dstpool/fs1         compression  gzip-9    -         inherited from dstpool
dstpool/fs1@snap1   compression  -         -         -
dstpool/vol1        compression  gzip-9    -         inherited from dstpool
dstpool/vol1@snap1  compression  -         -         -

Motivation and Context

Fix #1350
Also
https://www.illumos.org/issues/2745
https://www.illumos.org/issues/3753

How Has This Been Tested?

Test case added to the ZTS

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the ZFS on Linux code style requirements.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • All commit messages are properly formatted and contain Signed-off-by.
  • Change has been approved by a ZFS on Linux member.

@mention-bot
Copy link

@loli10K, thanks for your PR! By analyzing the history of the files in this pull request, we identified @pcd1193182, @ahrens and @danmcd to be potential reviewers.

Copy link
Contributor

@pcd1193182 pcd1193182 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for picking up where the previous implementation left off. I suspect that we're going to need to have a few more discussion and do some coordination between the proposed behavior here and the behavior that the rest of the OpenZFS community feels would be best. I've left some inline comments in addition to responses to some of your high level questions:

  • The man page wording is alright, except in the way that I noted below.
  • Linux doesn't have zones, so I don't think the zone property is important for you right now, though that question would probably be best answered by a more experienced linux developer.
  • That should be checked in libzfs. The kernel should also behave sanely in the event that such properties are passed in (probably by returning failure); I don't know how it would react today.
  • Personal preferences aside, consistency across platforms is important. The decision as to whether it inherits should probably be based on a discussion with individuals from all the OpenZFS platforms. I know @ahrens had some feelings on that, based on the previous ZoL patch commentary.

Overall, I think that the patch as it stands now is going to be very confusing to end users; if they try to pass -o to a zfs receive of a send stream that isn't compound, it will silently ignore all their -os and -xs, except for -o origin, and the man page says nothing about that. The only way for them to figure out what is happening and why would be to read the source. That's in addition to my notes about the behavior of inheritance above.

@@ -2725,10 +2725,137 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
return (needagain);
}

/*
* Update stream_avl based on override properties:
* 1. remove properties from "props" e "snapprops" nvlists if they are present
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and" instead of "e"

if (avl_is_empty(stream_avl) || nvlist_empty(props))
return (0); /* No properties to override */

VERIFY(nvlist_alloc(&noprops, NV_UNIQUE_NAME, 0) == 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use fnvlist_alloc() here.

props_override(libzfs_handle_t *hdl, avl_tree_t *stream_avl, nvlist_t *props,
recvflags_t *flags, const char *errbuf)
{
nvlist_t *noprops;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this called noprops? It appears to be storing a list of properties to use to override. override_props would seem like a better name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Override Props ... override_props would definitely be better but i started using abbreviated variable names because keeping all these nested loop (to deal with nested nvlist) in 80 cols was becoming a real challange ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nesting can be reduced by doing if (nvlist not present) continue; <handle nvlist> rather than if (nvlist present) {<handle nvlist>}. In any case, this particular variable doesn't appear deep in the nesting, so it can afford to have a longer name. I understand the desire to avoid overly-verbose names, though; this is the only one whose name I really have issue with.

* added (-o) properties to be later processed by zfs_valid_proplist().
*/
nvp = nvlist_next_nvpair(props, NULL);
while (nvp != NULL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer a for loop for iterating over nvlists, but that's not a big deal.

(fsn = avl_walk(stream_avl, fsn, AVL_AFTER))) {
nvlist_t *nvl = NULL;

if (0 == nvlist_lookup_nvlist(fsn->fn_nvfs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I general, use fnvlist functions whenever possible.

@@ -3755,7 +3887,8 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
DMU_COMPOUNDSTREAM);
return (zfs_receive_package(hdl, infd, tosnap, flags, &drr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like we don't use these properties in the case of a normal zfs send streams, we only use them for zfs send streams created with -R, -I, and -p? That seems like an unfortunate restriction, to me. At the very least, the man page should explicitly state that these arguments will be ignored in this case (except for -o origin, which will continue to work), and the receive should fail with a message that setting properties for non-compound send streams is invalid (ideally in more user-friendly and understandable words).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not intentional and i will try to fix it: it's my first time reading zfs_receive_*() functions and it was kind of confusing trying to understand the code path of a zfs recv ... sorry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the libzfs_sendrecv code is hard to trace. Most of it is because of the way that -R and -I were implemented, and the "compound stream" idea. If you modify zfs_receive_one() to take a props nvlist, and combine that with the property nvlist handling code already present, that should work.

Copy link
Contributor

@pcd1193182 pcd1193182 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you've addressed all my specific issues; thanks for that! I think that this point the thing remaining to work on is the inheritance behavior.

@loli10K
Copy link
Contributor Author

loli10K commented Oct 31, 2016

@pcd1193182 i will try to get my hands on (somewhat new) a Solaris box to study its zfs recv inheritance behaviour.

@loli10K
Copy link
Contributor Author

loli10K commented Nov 8, 2016

I managed to get ahold of a Solaris 11.3 iso just to discover it behaves in a very different way: the idea was to implement this feature changing the contents of the source stream as we read it and override (-o) or remove (-x) properties just before we actually receive them.

What the Solaris implementation seems to do is to always (even with -o|-x) receive the original properties from them source stream and then apply custom properties locally: this way we get to keep both original (source=received) and override/excluded (source=local/default) properties.

root@solaris:~# mkfile 128m /tmp/zpool.dat
root@solaris:~# zpool create tank /tmp/zpool.dat
root@solaris:~# zfs create tank/send
root@solaris:~# zfs set atime=off tank/send
root@solaris:~# zfs set compression=off tank/send
root@solaris:~# zfs snap -r tank/send@snap1
root@solaris:~# zfs send -p tank/send@snap1 > /tmp/snap_full.dat
root@solaris:~# zfs recv -x atime -o compression=gzip tank/recv < /tmp/snap_full.dat
root@solaris:~# zfs get -r -o all atime tank
NAME             PROPERTY  VALUE  RECEIVED  SOURCE
tank             atime     on     -         default
tank/recv        atime     on     off       default
tank/recv@snap1  atime     -      -         -
tank/send        atime     off    -         local
tank/send@snap1  atime     -      -         -
root@solaris:~# zfs get -r -o all compression tank
NAME             PROPERTY     VALUE  RECEIVED  SOURCE
tank             compression  off    -         default
tank/recv        compression  gzip   off       local
tank/recv@snap1  compression  -      -         -
tank/send        compression  off    -         local
tank/send@snap1  compression  -      -         -
root@solaris:~# 

This is probably going to require much more logic/code (maybe also in zfs_ioc_recv?) to get things working the same way Solaris ZFS does.

@ahrens
Copy link
Member

ahrens commented Nov 14, 2016

@loli10K Sounds like the Solaris way is similar to:

  1. set received properties (as we do today)
  2. set local properties (based on -x / -o)
  3. receive data

I think the semantics of that are nicer than the current implementation.

@loli10K
Copy link
Contributor Author

loli10K commented Nov 14, 2016

I think the semantics of that are nicer than the current implementation

I think that too, i'm going to try to fix this properly but i'll take some time: i started this while on vacation when i had a lot of spare time but now i'm back to a full time job and can only work in the evening/weekends.

Also i'm struggling to understand some of the code i've never used (basically most of it) so i'm doing some studying before rewriting this implementation. For instance, what is zfs_cmd_t.zc_nvlist_conf used for? The code says "legacy ioctls which haven't been converted to the new method" ... what is "the new method"? Am i allowed to use legacy stuff in new code?

@ahrens
Copy link
Member

ahrens commented Nov 14, 2016

@loli10K

i'm going to try to fix this properly but i'll take some time

Great, thanks for your continued work on this!

what is zfs_cmd_t.zc_nvlist_conf used for

This is used to pass the pool config (which specifies vdev configuration) between userland and kernel. This is typically used for /sbin/zpool commands (e.g. zpool add, zpool create). I don't think you would want to use it for zfs receive. See uses of zc_nvlist_conf in zfs_ioctl.c, and callers of zcmd_write_conf_nvlist() in libzfs.

what is "the new method"?

The new ioctl method uses only a small subset of the fields in the zfs_cmd_t. See the large comment at the beginning of zfs_ioctl.c for details.

Am i allowed to use legacy stuff in new code?

If you are modifying an existing ioctl (e.g. zfs_ioc_recv()), you can keep it the same, or convert it to the new style. If you are adding a new ioctl, please use the new style (i.e. zfs_ioctl_register()).

@behlendorf
Copy link
Contributor

I think the semantics of that are nicer than the current implementation.

I tend to agree.

i'm going to try to fix this properly but i'll take some time

Thanks! It's greatly appreciated.

you can keep it the same, or convert it to the new style.

For backwards compatibility reasons a new style zfs_ioc_recv_new() was implemented for Linux in commit 43e52ed. See the commit message for details. You should be able to extend this new interface as needed. Be aware this new ioctl hasn't yet been pushed upstream, but it should be.

@loli10K
Copy link
Contributor Author

loli10K commented Nov 21, 2016

I'm sorry to bother you all (again) but i could really use some help: the way i see it this has to be done in zfs_ioc_recv(), which is probably what the solaris ZFS implementation does ("internal inherit" in zpool history?):

root@solaris:~# POOLNAME='ztp'
root@solaris:~# TMPDIR='/tmp'
root@solaris:~# zpool destroy $POOLNAME
root@solaris:~# mkfile 65m $TMPDIR/zpool.dat
root@solaris:~# zpool create $POOLNAME $TMPDIR/zpool.dat
root@solaris:~# zfs create $POOLNAME/send
root@solaris:~# zfs create $POOLNAME/send/sub1
root@solaris:~# zfs set atime=off $POOLNAME/send
root@solaris:~# zfs set compression=off $POOLNAME/send/sub1
root@solaris:~# zfs snap -r $POOLNAME/send@snap1
root@solaris:~# zfs send -R $POOLNAME/send@snap1 > $TMPDIR/snap_full.dat
root@solaris:~# zfs recv -x atime -o compression=gzip -o copies=2 $POOLNAME/recv < $TMPDIR/snap_full.dat
root@solaris:~# zpool history -i
History for 'ztp':
2016-11-21.14:17:48 zpool create ztp /tmp/zpool.dat
2016-11-21.14:17:48 [internal pool create txg:5] pool spa 37; zfs spa 37; zpl 6; uts solaris 5.11 11.3 i86pc
2016-11-21.14:17:48 [internal property set txg:5] $share2=3 dataset = 18
2016-11-21.14:17:48 [internal create txg:6] dataset = 33
2016-11-21.14:17:48 zfs create ztp/send
2016-11-21.14:17:48 [internal property set txg:7] $share2=3 dataset = 33
2016-11-21.14:17:48 [internal create txg:8] dataset = 40
2016-11-21.14:17:48 zfs create ztp/send/sub1
2016-11-21.14:17:48 [internal property set txg:9] $share2=3 dataset = 40
2016-11-21.14:17:48 [internal property set txg:10] atime=0 dataset = 33
2016-11-21.14:17:48 zfs set atime=off ztp/send
2016-11-21.14:17:48 [internal property set txg:11] compression=2 dataset = 40
2016-11-21.14:17:48 zfs set compression=off ztp/send/sub1
2016-11-21.14:17:48 [internal snapshot txg:12] dataset = 44
2016-11-21.14:17:48 [internal snapshot txg:12] dataset = 48
2016-11-21.14:17:48 zfs snap -r ztp/send@snap1
2016-11-21.14:17:48 [internal user hold txg:13] <.send-1993-1> temp = 1 dataset = 48
2016-11-21.14:17:48 [internal user hold txg:14] <.send-1993-1> temp = 1 dataset = 44
2016-11-21.14:17:48 [internal user release txg:15] <.send-1993-1> 0 dataset = 48
2016-11-21.14:17:48 [internal user release txg:15] <.send-1993-1> 0 dataset = 44
2016-11-21.14:17:48 [internal replay_full_sync txg:16] dataset = 57
2016-11-21.14:17:48 [internal property set txg:17] $hasrecvd=0 dataset = 57
2016-11-21.14:17:48 [internal property set txg:18] compression=10 dataset = 57
2016-11-21.14:17:48 [internal property set txg:18] copies=2 dataset = 57
2016-11-21.14:17:48 [internal property set txg:19] $share2=3 dataset = 57
2016-11-21.14:17:48 [internal inherit txg:19] atime=1 dataset = 57
2016-11-21.14:17:48 [internal property set txg:19] atime=1 dataset = 57
2016-11-21.14:17:48 [internal snapshot txg:23] dataset = 61
2016-11-21.14:17:48 zfs recv -x atime -o compression=gzip -o copies=2 ztp/recv
2016-11-21.14:17:48 [internal property set txg:24] $hasrecvd=0 dataset = 61
2016-11-21.14:17:48 [internal replay_full_sync txg:25] dataset = 69
2016-11-21.14:17:48 [internal property set txg:26] $hasrecvd=0 dataset = 69
2016-11-21.14:17:48 [internal inherit txg:27] compression=10 dataset = 69
2016-11-21.14:17:48 [internal inherit txg:27] copies=2 dataset = 69
2016-11-21.14:17:48 [internal property set txg:28] compression=10 dataset = 69
2016-11-21.14:17:48 [internal property set txg:28] $share2=3 dataset = 69
2016-11-21.14:17:48 [internal snapshot txg:32] dataset = 73
2016-11-21.14:17:48 [internal property set txg:33] $hasrecvd=0 dataset = 73

That said, i have some questions:

  1. how can we know, from the context of zfs_ioc_recv(), if we are receiving the top-level dataset from a replication stream? I found that we had zc_top_ds in zfs_cmd_t, but that field was removed in illumos/illumos-gate@3b2aab1
  2. are we even allowed to look at the solaris implementation or this could be misinterpreted as reverse engineering?

@ahrens
Copy link
Member

ahrens commented Nov 21, 2016

On Mon, Nov 21, 2016 at 5:54 AM, LOLi notifications@github.com wrote:

how can we know, from the context of zfs_ioc_recv(), if we are
receiving the top-level dataset from a replication stream? I found that we
had zc_top_ds in zfs_cmd_t, but that field was removed in
illumos/illumos-gate@3b2aab1
illumos/illumos-gate@3b2aab1

I'm not sure. Can you back up a step and explain why you need to know
that? I assume you're considering how to implement "zfs send -R ... | zfs
receive -o prop=val ...". A property may be specified both on the command
line (and should be set "locally"), and in the send stream (and should be
set "received"). How will userland communicate that to the kernel?

  1. are we even allowed to look at the solaris implementation or this
    could be misinterpreted as reverse engineering?

I don't know, and I don't know if what you've mentioned above would be
considered looking at implementation details or not.

--matt

@loli10K
Copy link
Contributor Author

loli10K commented Nov 21, 2016

@ahrens

I'm not sure. Can you back up a step and explain why you need to know that?

When we are processing properties in zfs_ioc_recv() from a replication stream we need to set "override" props as "local" only on the top-level dataset and instead inherit them on the received children, right? So we need to know if we're processing the parent dataset or a child ...

I assume you're considering how to implement "zfs send -R ... | zfs receive -o prop=val ...".

Yes

A property may be specified both on the command line (and should be set "locally"), and in the send stream (and should be set "received"). How will userland communicate that to the kernel?

I was going to expand the existing zfs_ioc_recv() with a new nvlist of "override" props. These props gets processed from the kernel along with the "received" props. I just need to know if they must be set "locally" (received top-level) or "inherited" (received child).

I don't know, and I don't know if what you've mentioned above would be considered looking at implementation details or not.

Sorry, i don't want to cause problems for the project ...

@ahrens
Copy link
Member

ahrens commented Nov 21, 2016

@loli10K

we need to set "override" props as "local" only on the top-level dataset and instead inherit them on the received children, right? So we need to know if we're processing the parent dataset or a child

Right. I think that libzfs knows that. I think that the kernel should not have a concept of receiving top vs child datasets. Instead, userland should express to the kernel what properties should be set/inherited locally/received.

I would suggest that the user->kernel interface be: Receive this send stream, and set these properties locally, and set these other properties as received. ("set" here also encompasses the functionality of "inherit", which IIRC is implemented as "set to empty value").

So when libzfs is processing the top-level dataset, it would make the ioctl passing the CLI args as "set these properties locally". And when libzfs is processing children datasets, it would make the ioctl passing the CLI args as "inherit (set to empty value) these properties locally".

@loli10K
Copy link
Contributor Author

loli10K commented Nov 21, 2016

Right. I think that libzfs knows that. I think that the kernel should not have a concept of receiving top vs child datasets.
I would suggest that the user->kernel interface be [...]

Thanks @ahrens , i was probably on the wrong track trying to do the heavy lifting from the kernel, i will try to implement it this way.

@@ -613,6 +613,9 @@ recv_impl(const char *snapname, nvlist_t *props, const char *origin,
if (props != NULL)
fnvlist_add_nvlist(innvl, "props", props);

if (cmdprops != NULL)
fnvlist_add_nvlist(innvl, "cmdprops", cmdprops);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better name for this might be localprops, i.e. properties to set locally.

@@ -613,6 +613,9 @@ recv_impl(const char *snapname, nvlist_t *props, const char *origin,
if (props != NULL)
fnvlist_add_nvlist(innvl, "props", props);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might rename the argument to recvdprops to distinguish it from localprops. (Probably not worth trying to change the nvlist key though, for backwards-compatibility with older kernels.)

packed = fnvlist_pack(props, &size);
allprops = fnvlist_alloc();
if (props != NULL)
fnvlist_add_nvlist(allprops, "props", props);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I guess this is going to confuse older kernels anyway... @behlendorf can weigh in on the right strategy here.

man/man8/zfs.8 Outdated
.ad
.sp .6
.RS 4n
Creates a snapshot whose contents are as specified in the stream provided on standard input. If a full stream is received, then a new file system is created as well. Streams are created using the \fBzfs send\fR subcommand, which by default creates a full stream. \fBzfs recv\fR can be used as an alias for \fBzfs receive\fR.
.sp
If an incremental stream is received, then the destination file system must already exist, and its most recent snapshot must match the incremental stream's source. For \fBzvols\fR, the destination device link is destroyed and recreated, which means the \fBzvol\fR cannot be accessed during the \fBreceive\fR operation.
.sp
When a snapshot replication package stream that is generated by using the \fBzfs send\fR \fB-R\fR command is received, any snapshots that do not exist on the sending location are destroyed by using the \fBzfs destroy\fR \fB-d\fR command.
When a snapshot replication package stream that is generated by using the \fBzfs send\fR \fB-R\fR command is received, any snapshots that do not exist on the sending location are destroyed by using the \fBzfs destroy\fR \fB-d\fR command. If \fB-o\fR \fIproperty\fR=\fIvalue\fR or \fB-x\fR \fIproperty\fR is specified, it applies to the effective value of the property throughout the entire subtree of replicated datasets. Effective property values may be set or inherited, depending on the property and whether the dataset is the topmost in the replicated subtree. Received properties are retained in spite of being overridden and may be restored with \fBzfs inherit\fR \fB-rS\fR.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say just -S rather than -rS, since the -r is optional and may or may not apply depending on what they are trying to do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably makes sense for the new text to be in its own paragraph.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's implied (to experts) by "applies to the effective value throughout", but it might be good to explicitly state what will happen with -R streams: The property will be set (by -x) or inherited (by -o) on the topmost filesystem that is received. In descendant datasets, if the property is set by the send stream, we will override that by forcing the property to be inherited (from the topmost filesystem).

man/man8/zfs.8 Outdated
.ad
.sp .6
.RS 4n
Sets the specified property as if the command \fBzfs set\fR \fIproperty\fR=\fIvalue\fR was invoked at the same time the received dataset was created from the non-incremental send stream or updated from the incremental send stream.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might add: When receiving a stream from zfs send -R, causes the property to be inherited by all descendant datasets, as through zfs inherit property was run on any descendant datasets that have this property set on the sending system.

man/man8/zfs.8 Outdated
.sp
Any editable ZFS property can also be set at receive time. Set-once properties bound to the received data, such as \fBnormalization\fR and \fBcasesensitivity\fR, cannot be set at receive time even when the datasets are newly created by zfs receive.
.sp
Multiple \fB-o\fR options can be specified. An error results if the same property is specified in multiple \fB-o\fR or \fB-x\fR options.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about: "The -o option may be specified multiple times, for different properties."

zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin,
nvlist_t *props, boolean_t force, boolean_t resumable, int input_fd,
zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
nvlist_t *cmdprops, boolean_t force, boolean_t resumable, int input_fd,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmdprops -> localprops

// fnvlist_add_string(xprops, name, "");
// fnvlist_add_boolean(xprops, name);
dsl_prop_inherit(tofs, name,
ZPROP_SRC_INHERITED);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of unfortunate performance characteristics here, if there are a bunch of -x options, since this does a txg_wait_synced(0). But I suppose it's OK for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, i know, i tried to use zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, xprops, *errors) (commented out after the loop) but without much success so i opted for this "temporary" solution. The code, right now, just tries to mimic the correct inheritance behaviour. Additionally, not using zfs_set_prop_nvlist, we are also bypassing perms checks, so this will have to be reworked anyway.

@@ -4285,8 +4306,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin,
/*
* On error, restore the original props.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also be trying to restore the original localprops? Not sure...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should, i'm still kind of struggling to come up with a way to stash away all existing properties (recvd, local and inherited) in a sane way that would not involve too many different nvlists and loops on restore.

@loli10K
Copy link
Contributor Author

loli10K commented Dec 5, 2016

So while i was trying to figure out a way to restore properties on error i came across this strange ... behaviour: when i force an error in zfs_ioc_recv_impl() existing received properties are not restored. This happens with zfs-0.7.0-rc2 and doesn't seem to be related to the changes in this PR.

root@debian-8-zfs:~# #
root@debian-8-zfs:~# # setup
root@debian-8-zfs:~# export POOLNAME='testpool'
root@debian-8-zfs:~# export TMPDIR='/var/tmp'
root@debian-8-zfs:~# mountpoint -q $TMPDIR || mount -t tmpfs tmpfs $TMPDIR
root@debian-8-zfs:~# zpool destroy $POOLNAME
root@debian-8-zfs:~# fallocate -l 65m $TMPDIR/zpool.dat
root@debian-8-zfs:~# zpool create $POOLNAME $TMPDIR/zpool.dat
root@debian-8-zfs:~# zfs create $POOLNAME/send
root@debian-8-zfs:~# zfs set compression=on $POOLNAME/send
root@debian-8-zfs:~# zfs snap -r $POOLNAME/send@snap1
root@debian-8-zfs:~# zfs send -R $POOLNAME/send@snap1 > $TMPDIR/snap_full.dat
root@debian-8-zfs:~# zfs recv $POOLNAME/recv < $TMPDIR/snap_full.dat
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # received compression is 'on'
root@debian-8-zfs:~# zfs get -r -o all compression $POOLNAME/recv
NAME                 PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv        compression  on        on        received
testpool/recv@snap1  compression  -         -         -
root@debian-8-zfs:~# zfs snap -r $POOLNAME/send@snap2
root@debian-8-zfs:~# zfs snap -r $POOLNAME/send@snap3
root@debian-8-zfs:~# zfs set compression=gzip-2 $POOLNAME/send
root@debian-8-zfs:~# zfs send -RI $POOLNAME/send@snap1 $POOLNAME/send@snap2 > $TMPDIR/snap_incr1.dat
root@debian-8-zfs:~# zfs set compression=gzip-3 $POOLNAME/send
root@debian-8-zfs:~# zfs send -RI $POOLNAME/send@snap2 $POOLNAME/send@snap3 > $TMPDIR/snap_incr2.dat
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # now receive incrementals
root@debian-8-zfs:~# zfs recv -F $POOLNAME/recv < $TMPDIR/snap_incr1.dat
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # received compression is 'gzip-2'
root@debian-8-zfs:~# zfs get -r -o all compression $POOLNAME/recv
NAME                 PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv        compression  gzip-2    gzip-2    received
testpool/recv@snap1  compression  -         -         -
testpool/recv@snap2  compression  -         -         -
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # inject err in zfs_ioc_recv_impl(), fail to receive incr with compression='gzip-3'
root@debian-8-zfs:~# stap \
>    -d kernel -d zfs -d spl \
>    -g \
>    -c bash -c "zfs recv -F $POOLNAME/recv < $TMPDIR/snap_incr2.dat" \
> <(echo '
> #!/usr/bin/env stap
> probe module("zfs").function("zfs_ioc_recv_impl").call
> {
>    printf(" --> %s\n", probefunc());
>    $zfs_ioc_recv_inject_err = 1;
> }
> ')
cannot receive incremental stream: permission denied
WARNING: Child process exited with status 1
 --> zfs_ioc_recv_impl
WARNING: /usr/bin/staprun exited with status: 1
Pass 5: run failed.  [man error::pass5]
Tip: /usr/share/doc/systemtap/README.Debian should help you get started.
root@debian-8-zfs:~# # zfs receive failed, compression should be 'gzip-2' ... right?
root@debian-8-zfs:~# zfs get -r -o all compression $POOLNAME/recv
NAME                 PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv        compression  off       -         default
testpool/recv@snap1  compression  -         -         -
testpool/recv@snap2  compression  -         -         -
testpool/recv@snap3  compression  -         -         -
root@debian-8-zfs:~# 

Script for the lazy (like myself) here: https://gist.github.com/loli10K/3828245287eb6405bfc661c17a458166.

Unfortunately i cannot test this on Illumos because i don't know how to force an error in zfs_ioc_recv(), it'd be interesting to know if this is a ZoL specific behaviour.

EDIT: the problem is in dsl_props_set_sync_impl(): we don't preserve the nvpair name before looking up the ZPROP_VALUE in the nvlist, so nvpair_name() is always "value" and not the actual property name. Illumos shouldn't be affected by this.

@ahrens
Copy link
Member

ahrens commented Dec 8, 2016

@zettabot go

@loli10K
Copy link
Contributor Author

loli10K commented Dec 10, 2016

EDIT: too much content in a single post, moved outputs to external gists.

More testing later, another inconsistency: on Solaris, when we receive a stream with no properties set, we wipe existing received properties: https://gist.github.com/loli10K/4feb4ffe1a33d9ebd13914d241e7240a
On Illumos (tested on SmartOS) and ZoL we keep received properties instead: https://gist.github.com/loli10K/9dd4231bfe4b7a1879c214505d5043ff

Which is the "right" behaviour? I feel like we should keep existing properties if we don't clear them explicitly.

This brings to an implementation issue with the current user->kernel interface used to pass "local" override properties: right now i'm using a nvlist_t of props with these possible types:

  • DATA_TYPE_BOOLEAN => excluded (and/or inherited) property
  • everything else => locally set (override) properties

The problem is, when we want to keep locally set properties how do we convey that to the kernel? We can't force source=ZPROP_SRC_INHERITED on "excluded" props because that would wipe the local value. So the current interface seems too limited, i think i need at least 3 value types:

  • DATA_TYPE_BOOLEAN => excluded property
  • everything else (minus $something) => locally set (override) properties
  • that $something => inherited property
root@debian-8-zfs:~# # receive first full snap, recvd compression is 'gzip-1'
root@debian-8-zfs:~# zfs set compression=gzip-1 $POOLNAME/send
root@debian-8-zfs:~# zfs snap -r $POOLNAME/send@snap1
root@debian-8-zfs:~# zfs send -R $POOLNAME/send@snap1 > $TMPDIR/snap_full.dat
root@debian-8-zfs:~# zfs recv $POOLNAME/recv < $TMPDIR/snap_full.dat
root@debian-8-zfs:~# zfs get -o all compression $POOLNAME/recv
NAME           PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv  compression  gzip-1    gzip-1    received
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # receive the incr snap, recvd compression is 'gzip-2'
root@debian-8-zfs:~# zfs set compression=gzip-2 $POOLNAME/send
root@debian-8-zfs:~# zfs snap -r $POOLNAME/send@snap2
root@debian-8-zfs:~# zfs send -RI $POOLNAME/send@snap1 $POOLNAME/send@snap2 > $TMPDIR/snap_incr.dat
root@debian-8-zfs:~# #
root@debian-8-zfs:~# # recv has compression locally set
root@debian-8-zfs:~# zfs set compression=lz4 $POOLNAME/recv
root@debian-8-zfs:~# zfs get -o all compression $POOLNAME/recv
NAME           PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv  compression  lz4       gzip-1    local
root@debian-8-zfs:~# zfs recv -F -x compression $POOLNAME/recv < $TMPDIR/snap_incr.dat
root@debian-8-zfs:~# zfs get -o all compression $POOLNAME/recv
NAME           PROPERTY     VALUE     RECEIVED  SOURCE
testpool/recv  compression  off       gzip-2    default

@ahrens
Copy link
Member

ahrens commented Dec 15, 2016

More testing later, another inconsistency:
on Solaris, when we receive a stream with no properties set, we wipe existing received properties
On Illumos (tested on SmartOS) and ZoL we keep received properties instead

zfs send -R | zfs recv is supposed to make the (received) properties on the target system be the same as the effective properties on the source system. So I think that wiping the existing received properties is the correct behavior. The way I would think of this is: I changed the property on the source system (with zfs inherit), and zfs send -R is supposed to transmit my properties to the target. In this case it seems to have not done that.

FWIW, I thought that was the original design and behavior of send -R and "received" properties, so it's possible that it used to work "right" in illumos, and at some point we broke it.

This issue is separate from the new -o / -x code, right?

@loli10K
Copy link
Contributor Author

loli10K commented Dec 15, 2016

@ahrens

So I think that wiping the existing received properties is the correct behavior.

Ok, i'm going to try and fix that too then. (EDIT2: fixed in master)

This issue is separate from the new -o / -x code, right?

Yes, i reproduced it on ZoL zfs-0.7.0-rc2 (which was tagged before i even started working on this) and SmartOS 20160527T033529Z (which i don't even know how to build yet, so definitely not related to the new code).

EDIT: Maybe broken in illumos/illumos-gate@3b2aab1? I can still reproduce the problem in smartos until 20130222T000747Z which is the newer version that seems to work as intended.

Regarding the other issue, could you give me some guidance too? I don't want to implement this in a way that would not be accepted upstream because that ultimately won't benefit some users.

To recap, we need to pass to the kernel both "inherited" and "excluded" properties at the same time (same nvlist) if we want to keep locally set properties on receive, so we can't use DATA_TYPE_BOOLEAN for both of them. We could:

  • use another nvpair type (DATA_TYPE_BYTE?) to ask the kernel to set a certain property as inherited and use DATA_TYPE_BOOLEAN only for excluded ones
  • nest another 2 nvlist ("exclude" and "override") in "localprops" and use DATA_TYPE_BOOLEAN for both "inherited" and "excluded" properties
  • pass to the kernel all the properties from the command line and let it figure out when to set locally or inherit (see previous comment about zc_top_ds)
  • ???

@ahrens
Copy link
Member

ahrens commented Dec 29, 2016

@loli10K

To recap, we need to pass to the kernel both "inherited" and "excluded" properties at the same time (same nvlist) if we want to keep locally set properties on receive, so we can't use DATA_TYPE_BOOLEAN for both of them.

Why do we need to pass excluded properties to the kernel? Per my previous recommendation:

I would suggest that the user->kernel interface be: Receive this send stream, and set these properties locally, and set these other properties as received. ("set" here also encompasses the functionality of "inherit", which IIRC is implemented as "set to empty value").

More specifically, the nvlist passed to the kernel could contain:
"local_props" -> nvlist of prop -> value; if value is BOOLEAN, it means inherit
"received_props" -> nvlist of prop -> value; if value is BOOLEAN, it means inherit

@loli10K
Copy link
Contributor Author

loli10K commented Dec 29, 2016

@ahrens consider the following example:

# setup boilerplate
export POOLNAME='testpool'
export TMPDIR='/var/tmp'
mountpoint -q $TMPDIR || mount -t tmpfs tmpfs $TMPDIR
zpool destroy $POOLNAME
fallocate -l 65m $TMPDIR/zpool.dat
zpool create $POOLNAME $TMPDIR/zpool.dat
zfs create $POOLNAME/send
zfs set compression=gzip-1 $POOLNAME/send
zfs snap $POOLNAME/send@snap1
zfs send -R $POOLNAME/send@snap1 > $TMPDIR/snap_full.dat
zfs recv $POOLNAME/recv < $TMPDIR/snap_full.dat
#
# received compression is 'gzip-1'
zfs get -r -o all compression $POOLNAME/recv
#
# now set new compression on the sending ds
zfs set compression=gzip-2 $POOLNAME/send
zfs snap $POOLNAME/send@snap2
zfs send -RI $POOLNAME/send@snap1 $POOLNAME/send@snap2 > $TMPDIR/snap_incr.dat
#
# set compression locally on the received ds
zfs set compression=gzip-9 $POOLNAME/recv
#
# receive the incremental, but exclude 'compression' (we want receive it but the effective value should not be changed)
zfs recv -Fv -x compression $POOLNAME/recv < $TMPDIR/snap_incr.dat
#
# received compression should be 'gzip-2' (excluded values are still received)
# effettive compression should be still 'gzip-9' because it was set locally
zfs get -r -o all compression $POOLNAME/recv

How do we keep that compression=gzip-9 locally set and at the same time set received compression=gzip-2?

AIUI the idea is to set:

  • Received properites from the send stream : received_props -> { "compression" -> "value" }
  • Excluded (-x) properties : received_props -> { "compression" -> BOOLEAN } (inherit)
  • Overridden (-o) properties on top_ds : local_props -> { "compression" -> "value" }
  • Overridden (-o) properties on children : local_props -> { "compression" -> BOOLEAN } (inherit)

Those nvlists are NV_UNIQUE_NAME, so in the previous example i don't think we can set

received_props -> {
   "compression" -> "gzip-2",
   "compression" -> BOOLEAN,
}

to tell the kernel to set received compression=gzip-2 but also inherit so the effective value would remain unchanged.

Also we can't set

received_props -> {
   "compression" -> "gzip-2"
}
local_props -> {
   "compression" -> BOOLEAN
}

because that would wipe the locally set compression=gzip-9 as if we wanted to -o compression=something on a child ds.

With this method we need to know beforehand, from usermode, that compression is locally set so it doesn't need a BOOLEAN entry in the nvlist to force its effective value.

@ahrens
Copy link
Member

ahrens commented Dec 29, 2016

@loli10K I see, I thought that -x ignored the property from the send stream, but actually it's supposed to set it as a received property, and override it with a local set or inherit.

I think you're proposing that the ending on-disk state records:

  1. we have received value X
  2. we are ignoring received value
  3. we have locally set value Y

I don't think that is how it should work. I think that the manpage changes describe the behavior we want:

If a received property needs to be overridden, the effective value can be set or inherited, depending on the property.

In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged (since the received property is already overridden).

In your example, there is a local setting (gzip-9). In this case the -x doesn't change the behavior of zfs receive: we set the property from the stream as a received property, changing the received value from gzip-1 to gzip-2. The local setting of gzip-9 continues to override the received value, so the goal of -x is accomplished: the received value does not take effect.

If there was no local setting (it was implicitly inherited), then -x should cause us to add a local explicit inherit (and also set the property from the send stream as a received property).

Essentially, -o and -x have no effect on the received values, they only serve to make local settings/inheritances (which can override the received values).

@loli10K
Copy link
Contributor Author

loli10K commented Dec 29, 2016

In your example, there is a local setting (gzip-9). In this case the -x doesn't change the behavior of zfs receive: we set the property from the stream as a received property, changing the received value from gzip-1 to gzip-2. The local setting of gzip-9 continues to override the received value, so the goal of -x is accomplished: the received value does not take effect.

IF there was no local setting (it was implicitly inherited), then -x should cause us to add a local explicit inherit (and also set the property from the send stream as a received property).

Ok, so we need to decide whether to add a explicit local inherit (or not) based on existing local properties:

  • if we have a local compression don't force an explicit inherit, the received value does not take effect anyway.
  • if we don't have compression locally set we need to explicitly inherit it or the effective value would be the received one.

This force us to gather locally set properties before any receive to calculate if we need and explicit inherit.

EDIT: which is basically what i was trying to say in my last sentence:

With this method we need to know beforehand, from usermode, that compression is locally set so it doesn't need a BOOLEAN entry in the nvlist to force its effective value.

Setting a property source to ZPROP_SRC_INHERITED wipes the locally set value! From dsl_prop_set_sync_impl:

 709         case ZPROP_SRC_INHERITED:                                               
 710                 /*                                                              
 711                  * explicitly inherit                                           
 712                  * - remove propname                                            
 713                  * - set propname$inherit                                       
 714                  */                                                             
 715                 err = zap_remove(mos, zapobj, propname, tx);                    
 716                 ASSERT(err == 0 || err == ENOENT);                              
 717                 if (version >= SPA_VERSION_RECVD_PROPS &&                       
 718                     dsl_prop_get_int_ds(ds, ZPROP_HAS_RECVD, &dummy) == 0) {    
 719                         dummy = 0;                                              
 720                         VERIFY0(zap_update(mos, zapobj, inheritstr,             
 721                             8, 1, &dummy, tx));                                 
 722                 }                                                               
 723                 break;                                                          

@ahrens
Copy link
Member

ahrens commented Dec 29, 2016

@loli10K That's right.

If we want to avoid gathering that info in userland to figure out whether we should do "set received prop and also set local inherit" vs "set received prop", we could move that logic into the kernel. Essentially add a mode where the kernel will "locally inherit this property if it is not already set locally". That mode could apply to all "locally inherit" properties that are part of the receive (since -x is the only way we would do an "explicit local inherit" as part of a receive).

@loli10K
Copy link
Contributor Author

loli10K commented Dec 29, 2016

@ahrens the kernel is already going to gather existing props before the receive anyway since we need to restore them if anything goes wrong, it would probably be optimal to handle this logic into the kernel: we just need to convey that idea via the two local_props and received_props nvlists.

EDIT: ended up implementing it as suggested, in userland.

EDIT2: this consistently breaks the following test cases:

zfs_upgrade_003_pos
zfs_upgrade_004_pos
zfs_upgrade_005_pos
upgrade_userobj_001_pos

Also we still need to restore locally set properties on a failed receive (i just checked on sol11.3 and they do). This is going to get messy.

@loli10K
Copy link
Contributor Author

loli10K commented Feb 7, 2017

@ahrens could you please review this (again)? I know the github thingy still says "Changes requested" but i already implemented the last part (restore properties on error) along with a test case. I've also recently rebased on master, resolved all conflicts with the latest OpenZFS patches and renamed the test script to conform to the new (non-numeric) "standard" as suggested.

I also ported the test case (along with some of the utility functions from the ZTS) in a self-contained shell script and run it on a solaris box. Everything passes except a couple of tests: it does seem that zfs recv -x prop does not affect user properties on solaris. I couldn't find this documented anywhere and seems a bug to me.

if (prop == ZPROP_INVAL) {
if (zfs_prop_user(name) != B_TRUE) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"cannot override invalid property '%s'"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this function will be called for both -x and -o meaning you should probably update this message to something like "cannot override or exclude invalid property '%s'"

continue;

/* cannot override readonly and set-once properties */
if (zfs_prop_readonly(prop) == B_TRUE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could combine this if statement with the one bellow it and update the message here as well

@loli10K
Copy link
Contributor Author

loli10K commented Mar 23, 2017

If anyone still cares about this: i've rebased this PR on master to fix the build issues with mainline Linux, all tests seem to be passing now.

Also manual inspection of the buildbot console shows that zdb is crashing running the ZTS (in "zdb_001_neg"): i've opened a new PR to address that #5917.

Copy link
Contributor

@alek-p alek-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still like to see this integrated but it has taken me a while to get to the review. I'm yet to read through the changes in zfs_ioctl.c but hope to do that soon.

* Modifies the argument inplace.
*/
static int
parsepropname(nvlist_t *props, char *propname)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this could return B_TRUE or B_FALSE, aka a boolean_t as it's a failed or not type of return code.
This will also allow you to do "if (parsepropname(p, p2))" - without checking the return value. Looks like you were trying to be consistent with parseprop() which arguably should have been doing the same thing

int ret = 0;

if (nvlist_empty(cmdprops))
return (0); /* No properties to override */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment should probably be "No properties to override or exclude"?

if (nvlist_empty(cmdprops))
return (0); /* No properties to override */

*oxprops = fnvlist_alloc();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this freed, especially when we return an error?

toplevel = chopprefix[0] != '/';
if (drrb->drr_type == DMU_OST_ZVOL)
type = ZFS_TYPE_VOLUME;
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we be a bit more defensive here?
I'm thinking something like
else if (drrb->drr_type == DMU_OST_ZFS) type = ZFS_TYPE_FILESYSTEM; else goto out;
or something simular?

nvlist_free(props);
nvlist_free(rcvprops);

if (origprops != NULL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check for NULL is unnecessary as nvlist_free() does it internally

{
nvpair_t *nvp;
int err = 0;
const char *name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency, you should either define prop and name variables outside the while or inside

/*
* Check properties we were asked to override (both -o|-x)
*/
static int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this can return a boolean_t

if (zfs_prop_user(name) != B_TRUE) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"invalid property '%s'"), name);
err = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like you can simplify this function by doing return (1) here, or preferably a boolean, and avoiding err variable altogether.

man/man8/zfs.8 Outdated
.sp
If a received property needs to be overridden, the effective value can be set or inherited, depending on the property.
.sp
In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged (since the received property is already overridden).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the part in parens, to me, it just adds confusion.

man/man8/zfs.8 Outdated
.sp
In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged (since the received property is already overridden).
.sp
All \fB-o\fR restrictions apply equally to \fB-x\fR.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is pretty ambiguous, I would add more info here

@loli10K
Copy link
Contributor Author

loli10K commented Mar 27, 2017

@alek-p thanks for taking the time to review this! I'll try to get those things fixed as requested except the man pages: i'm not good at explaining things, my contribution on that would only add more confusion. Sorry about that.

Also, before you read the code in "zfs_ioctl.c", i can only say that i'm sorry for all the trouble and wish you good luck!

@ahrens
Copy link
Member

ahrens commented Mar 30, 2017

@loli10K Thanks for all your work on this. I will do another review and hope to have that done next week.

Copy link
Member

@ahrens ahrens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good!

"<filesystem|volume|snapshot>\n"
"\treceive [-vnsFu] [[-o property=<value>] | "
"[-x property]] ... \n"
"\t [-d | -e] [-o origin=<snapshot>] <filesystem>\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove [-o origin=<snapshot>] since that's covered by -o property=value (at least in the usage message -- we'll want to make a special note of being able to set origin in the manpage).

For -o property=<value>, I think it should be -o <property>=<value> since property is not a literal value.

With [[-o prop=val] | [-x prop]] ..., I think you're trying to indicate that for a given property, you can either use -o or -x, but not both. But I'm not sure this is really clear, and it may just be adding to the confusion. Consider changing to the simpler [-o prop=val] ... [-x prop] ...

* cmdprops: input properties from command line
* origprops: original properties if the destination exists, NULL otherwise
* oxprops: output override (-o) and excluded (-x) properties
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the oxprops differ from cmdprops? They both have the properties that are excluded and overridden, right?

* Prepare a new nvlist of properties that are to override (-o) or be excluded
* (-x) from the received dataset
* cmdprops: input properties from command line
* origprops: original properties if the destination exists, NULL otherwise
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about properties currently set on the target filesystem, if it exists (as opposed to the original properties in the send stream). Does this include both locally-set and received properties?

/* second pass: process "-o" properties */
nvp = NULL;
while ((nvp = nvlist_next_nvpair(voprops, nvp)) != NULL) {
fnvlist_add_nvpair(*oxprops, nvp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use nvlist_merge() here?

switch (nvpair_type(nvp)) {
case DATA_TYPE_BOOLEAN: /* -x property */
if (!nvlist_exists(origprops, name))
fnvlist_add_nvpair(*oxprops, nvp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think origprops includes both received and locally-set properties. So if you do -x, and the property is a already set as a received property, then we ignore the -x, and allow the send stream to change the received property to the new value? The -x is supposed to ensure that the property setting from the stream does not take effect, right? Do we do that by doing a local explicit inherit, to override the received value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sent properties should be still received and updated to the new value, even if -x is specified, but you're right we need to make sure they're also explicitly inherited. I don't think the current test case cover this, i'm updating that too.

@@ -4356,6 +4472,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin,
* inputs:
* zc_name name of containing filesystem (unused)
* zc_nvlist_src{_size} nvlist of properties to apply
* zc_nvlist_conf{_size}nvlist of properties to override or exclude
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you describe this more completely? I think we interpret boolean entries differently than number/string.

@@ -810,13 +819,13 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
* The 'errors' nvlist contains an entry for each unapplied received
* property. Callers are responsible for freeing this nvlist.
*/
int lzc_receive_one(const char *snapname, nvlist_t *props,
int lzc_receive_one(const char *snapname, nvlist_t *props, nvlist_t *cmdprops,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document the cmdprops somewhere.

For backwards compatibility, consider leaving this function signature unchanged, and adding a new function e.g. lzc_receive_with_cmdprops. This isn't a strict requirement at this point, but it seems like the nice thing to do in cases where it isn't difficult.

if (!zfs_prop_inheritable(prop))
continue;
} else if (!zfs_prop_user(name))
continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for cstyle, use braces on all if/else branches

So, we silently ignore -x of non-native, non-user properties, and also we ignore native, non-inheritable properties? What's the rationale for ignoring these vs. returning an error (and relying on userland to filter them out)?

How does -x <non-inheritable property> work? (e.g. -x quota)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use braces on all if/else branches

Even on single line conditionals?

How does -x <non-inheritable property> work?

I think we should deal with this in userland, if the user ask us to -x <non-inheritable property> we could pass this information to the kernel as -o <default-value-of-non-inheritable property> on both the root dataset and descendants: would this be acceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even on single line conditionals?

No, just when one of the if/else branches has braces - basically either all or none of the if/else bodies should have braces.

A use case for -x quota might be that they have already set the quota locally, and they want to ignore whatever the sending system says about quotas. -o quota=X isn't always a good solution, because they could have a send -R stream, and want different quotas for different child filesystems. Even if there's only one filesystem, it would be annoying to have to specify the quota every time they receive.

I think a better semantic would be for -x quota (and other non-inheritable properties) to remove the property from the send stream. Thus for -x non-inheritable-property, we leave whatever value there already is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the property from the send stream

Isn't this the behaviour that came with the original patch from #1867 and that we tried to avoid to be as close as possible to the other implementation(s)?

Though i agree this seems a better, albeit different, behaviour and i'm ok with implementing it this way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle is that -x should cause the property value from the send stream to have no effect - which was not achieved for non-inheritable properties (and treating it as -o prop=default_value doesn't achieve it either).

Inheritable properties have the concept of a received vs locally-set version, which we want to honor (but #1867 did not). Non-inheritable properties (e.g. quota) have a single value, so there's really no choice about it.

(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL,
oprops, *errors);
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED,
xprops, *errors);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're just going to sort them into -x and -o in the kernel, we could instead have userland sort them, passing down 2 nvlists (perhaps as child nvlists). But feel free to take this as just a suggestion.

@@ -4429,7 +4554,8 @@ zfs_ioc_recv(zfs_cmd_t *zc)
/*
* innvl: {
* "snapname" -> full name of the snapshot to create
* (optional) "props" -> properties to set (nvlist)
* (optional) "recvprops" -> received properties to set (nvlist)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change seems inaccurate, it's still "props"

@loli10K
Copy link
Contributor Author

loli10K commented Apr 7, 2017

@alek-p @ahrens i tried to keep every requested change in a separate commit hoping that would help your review process: i'm going to wait for your input before i squash and rebase on master.

Also the test case has been updated and should be more readable now.

@@ -4472,7 +4472,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
* inputs:
* zc_name name of containing filesystem (unused)
* zc_nvlist_src{_size} nvlist of properties to apply
* zc_nvlist_conf{_size}nvlist of properties to override or exclude
* zc_nvlist_conf{_size}nvlist of properties to exclude (DATA_TYPE_BOOLEAN) and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be some whitespace after {_size}

typeset streamfile_repl1=/var/tmp/streamfile_repl1.$$
typeset streamfile_repl2=/var/tmp/streamfile_repl2.$$
typeset streamfile_repl3=/var/tmp/streamfile_repl3.$$

function cleanup
{
log_must $RM $streamfile_full
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you merge with master you'll need to change the test script $RM->rm, etc.

* the send stream contains, for instance, a child ZVOL and
* we're trying to receive it with "-o atime=on"
*/
if (!zfs_prop_valid_for_type(prop, type, B_FALSE) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we continue; here if the property isn't valid for the type but it is recursive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this seems to work but then we can see that, for instance, properties like "atime" can be received on ZVOLs: https://gist.github.com/loli10K/f129d5b7f6ef4b45954857e361a5a618.

I'm working to fix this and update the test case.

@loli10K loli10K added the Type: Feature Feature request or new feature label May 3, 2017
@loli10K loli10K requested a review from alek-p May 3, 2017 17:33
@alek-p
Copy link
Contributor

alek-p commented May 4, 2017

sorry I've been lagging on this. I'll make sure to finish up reviewing by Monday.

Copy link
Contributor

@alek-p alek-p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for staying on top of this one.

This allows users to specify "-o property=value" to override and
"-x property" to exclude properties when receiving a zfs send stream.
Both native and user properties can be specified.

This is useful when using zfs send/receive for periodic
backup/replication because it lets users change properties such as
canmount, mountpoint, or compression without modifying the source.

References:
   https://www.illumos.org/issues/2745
   https://www.illumos.org/issues/3753

Signed-off-by: loli10K <ezomori.nozomu@gmail.com>
@behlendorf
Copy link
Contributor

@loli10K thanks for pushing this one forward. This LGTM as well, are there any additional changes you still want to make? If not we can get this merged, the test failures were all unrelated and I've resubmitted those runs.

@loli10K
Copy link
Contributor Author

loli10K commented May 9, 2017

@behlendorf the code should be good to go, i just wanted to port this to Illumos, post the patch on their issue tracker and try to get more testing and reviews but i've not yet had the chance to set up a fully working openindiana development box. I think we can merge this now and come back to it if/when we need to.

@behlendorf behlendorf merged commit a3eeab2 into openzfs:master May 9, 2017
@behlendorf
Copy link
Contributor

@loli10K sounds good. Merged.

@tcaputi
Copy link
Contributor

tcaputi commented May 22, 2017

Sorry. I know I'm a bit late to the party here. I was looking at the code a bit to see how it would interact with encryption and I noticed that -o / -x properties are not delayed in zfs_ioc_recv_impl(). Could anybody explain to me why this might be? This matters for encryption because keylocation is implemented as a delay property for several reasons.

@loli10K
Copy link
Contributor Author

loli10K commented May 23, 2017

@tcaputi my reasoning was that we don't need to delay those properties because if they were to prevent the receive operation from succeeding we could always apply them later with zfs set:

For instance, if the following fails (EDQUOT)

zfs recv -o refquota=1234 mypool/destfs < stream.dat

we could

zfs recv mypool/destfs < stream.dat
# rm /mypool/destfs/{foo,bar}
zfs set refquota=1234 mypool/destfs

To be honest i haven't read the new encryption code, can you clarify why keylocation needs to be delayed and in which case (-x, -o or both)?

@tcaputi
Copy link
Contributor

tcaputi commented May 23, 2017

In the encryption patch, keylocation is a property that tells zfs where it should look for encryption keys by default. The property can be set to either "prompt" or a file URI at the moment. This property can only be set for encrypted datasets.

The other relevant bit of background info is how raw (encrypted) receives work. The purpose of a raw receive is to allow the user to send their data encrypted exactly as it is on disk. This requires the dataset's master keys to be sent in the DRR_BEGIN payload. Unfortunately, because of the way that the receive code currently works, this payload is not processed until dmu_recv_stream(), and not in dmu_recv_begin() as I would have liked. As a result the dataset isn't actually encrypted when the code goes to set the keylocation property. Making the keylocation a delay property allowed me to bypass this problem fairly elegantly.

@loli10K
Copy link
Contributor Author

loli10K commented May 25, 2017

As a result the dataset isn't actually encrypted when the code goes to set the keylocation property

It's not entirely clear to me why this would be a problem, but again the real reason override properties are not delayed is because i did not see the need to: now that there's a real use case we can address it.

@loli10K loli10K deleted the issue-1350 branch May 25, 2017 15:40
@tcaputi
Copy link
Contributor

tcaputi commented Feb 9, 2018

@ptx0 I am aware of this. It's next on my list.

@loli10K loli10K restored the issue-1350 branch November 1, 2018 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature Feature request or new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

zfs receive does neither support -o property=value nor -x property
7 participants