Skip to content

Conversation

@ihoro
Copy link
Contributor

@ihoro ihoro commented Sep 21, 2025

Sponsored-by: SkunkWerks, GmbH

Motivation and Context

It targets #15710 feature request to add a read-only property to report name of the jail that mounted the dataset.

Description

The decisions made:

  • The 0 name is used for datasets that are not mounted or not jailed. The default zfs style is to use - for a missing value, but it's allowed to have a jail named like that. At the same time there is no way to name a jail as 0: jail: name cannot be numeric (unless it is the jid).
  • The mechanism remembers the name only, not jid. It can be treated as a caching mechanism to avoid time complexity increase. The reason is that all properties are prepared before listing only requested ones, and traversing all prisons (with allprison_lock) to lookup jail name by jid does not feel good for the cases with many jailed datasets and/or jails.
  • Because of the above, jail renaming is not supported -- it will still report the old name.
  • Nested jails are not supported for simplicity -- only non-jailed root can see actual value of the jail property, others are provided with 0. Such way it covers the requirement not to reveal jail name to the jail itself.
  • As long as the name is cached, it's okay to have a situation when the dataset is still mounted even though the jail reported by this property is no longer present. It's not expected to be a usual case.
  • The zfsprops.7 man page was extended to outline the property. It is mentioned within the section of tunables even though it is a read-only one, it seems to be better to keep it closer to the jailed property.

Hence, the mechanism is simple:

  • zfs_domount() caches pr_name
  • zfs_umount() clears the name

The proposed implementation covers usual cases without extra complexity and property retrieval performance impact. The opportunities for improvements are left for the future.

How Has This Been Tested?

Originally the patch was created months ago for 15-CURRENT, it was re-tested for the latest 16-CURRENT. The UI/UX demonstration:

# cat /etc/jail.conf
jail1 {
        host.hostname = "jail1.local";
        path = "/root/tmp/jailroot_15current_1";
        persist = true;
        mount.devfs = true;
        allow.mount = true;
        allow.mount.zfs = true;
        enforce_statfs = 1;
        exec.created = "/sbin/zfs jail jail1 p3/dataset2";
        exec.start = "/bin/sh /etc/rc.d/zfs start";
        exec.stop = "/bin/sh /etc/rc.d/zfs stop";
        exec.poststop = "/sbin/zfs unjail jail1 p3/dataset2";
        children.max = 99;
}

# zpool import -d tmp/p3 p3

# zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            yes      off     0
p3/dataset1  /p3/dataset1   yes      off     0
p3/dataset2  /dataset2_mnt  no       on      0
p3/dataset3  /dataset3_mnt  no       on      0

# service jail onestart jail1

# zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            yes      off     0
p3/dataset1  /p3/dataset1   yes      off     0
p3/dataset2  /dataset2_mnt  yes      on      jail1
p3/dataset3  /dataset3_mnt  no       on      0

# jexec jail1 zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            no       off     0
p3/dataset2  /dataset2_mnt  yes      on      0

# jexec jail1 jail -c persist name=child allow.mount=true allow.mount.zfs=true enforce_statfs=1
# zfs jail jail1.child p3/dataset3
# jexec jail1.child zfs mount p3/dataset3

# jexec jail1.child zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            yes      off     0
p3/dataset3  /dataset3_mnt  yes      on      0

# jexec jail1 zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            no       off     0
p3/dataset2  /dataset2_mnt  yes      on      0

# zfs list -o name,mountpoint,mounted,jailed,jail
NAME         MOUNTPOINT     MOUNTED  JAILED  JAIL
p3           /p3            yes      off     0
p3/dataset1  /p3/dataset1   yes      off     0
p3/dataset2  /dataset2_mnt  yes      on      jail1
p3/dataset3  /dataset3_mnt  yes      on      jail1.child

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)
  • Quality assurance (non-breaking change which makes the code more robust against bugs)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv)
  • Documentation (a change to man pages or other documentation)

Checklist:

@ikozhukhov
Copy link
Contributor

under illumos based platform we have zone property. probably we can prepare and use universal name for all platforms ?
zones are virtual environment where we can provide dataset and exclude it from global zone and others.
I think we can use zone property or rename it to more universal/applicable name and use it.
I see duplicate logic for the same properties.

@ihoro ihoro force-pushed the 0064-zfs-jail-prop branch from 9a8e5cb to a5e0913 Compare September 21, 2025 13:14
@ihoro
Copy link
Contributor Author

ihoro commented Sep 21, 2025

under illumos based platform we have zone property. probably we can prepare and use universal name for all platforms ? zones are virtual environment where we can provide dataset and exclude it from global zone and others. I think we can use zone property or rename it to more universal/applicable name and use it. I see duplicate logic for the same properties.

Thanks for taking this moment into consideration. Indeed, the cross-platform term in the codebase is zone, and it's turned out that in the past the decision was made to follow FreeBSD specific terminology when FreeBSD Jails support was added. Hence, we have already established interface with "jail" word: zfs jail, zfs unjail, zfs get jailed, zfs list -o jailed, etc. As long as it is directly related to the existing jailed feature, the FreeBSD users expect to have this new property named jail, otherwise it could be misleading.

This read-only property reports the name of the jail that mounted the jailed
dataset.
The "0" name is used for datasets that are not mounted or not jailed.
If a jail is renamed, the property will still report its old name from
Copy link
Contributor

Choose a reason for hiding this comment

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

We should mention the reasoning for "0". What do you think of?:

The "0" name is used for datasets that are not mounted or not jailed.
+ This differs from the normal ZFS convention to print dash ('-') for unset values,
+ since '-' can be a valid jail 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.

This is a great idea. Fixed, thanks.

@tonyhutter
Copy link
Contributor

  1. I saw this in the JSON output (zfs list -j ...):
        "jail": {
          "value": "0",
          "source": {
            "type": "LOCAL",
            "data": "-"
          }
        }

I would have expected type to be NONE since this is read-only.

  1. Could you update tests/zfs-tests/tests/functional/cli_root/zfs_jail/zfs_jail_001_pos.ksh with some simple tests to verify zfs list -o jail ... works?

@behlendorf behlendorf added the Status: Code Review Needed Ready for review and testing label Sep 25, 2025
zfsvfs->z_os->os_dsl_dataset->ds_jailname =
kmem_zalloc(strlen(pr->pr_name) + 1, KM_SLEEP);
strcpy(zfsvfs->z_os->os_dsl_dataset->ds_jailname,
pr->pr_name);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use kmem_strdup(pr->pr_name); to simplify this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. Fixed.

(ds->ds_jailname && INGLOBALZONE(curproc)) ?
ds->ds_jailname : "0"));
VERIFY0(nvlist_add_string(propval, ZPROP_SOURCE, setpoint));
VERIFY0(nvlist_add_nvlist(*nvp, "jail", propval));
Copy link
Contributor

Choose a reason for hiding this comment

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

The fnvlist_* wrappers should be used here, or better yet add the required error handling even if they're almost certain never to fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. It seems I followed the examples from dsl_prop_get_all_impl() itself. Migrated to fnvlist_*, thanks.

A read-only property to report name of the jail that mounted the
dataset.

Sponsored-by: SkunkWerks, GmbH
Signed-off-by: Igor Ostapenko <[email protected]>
@ihoro ihoro force-pushed the 0064-zfs-jail-prop branch from a5e0913 to 607e4ed Compare October 5, 2025 11:30
@ihoro
Copy link
Contributor Author

ihoro commented Oct 5, 2025

  1. I saw this in the JSON output (zfs list -j ...):
        "jail": {
          "value": "0",
          "source": {
            "type": "LOCAL",
            "data": "-"
          }
        }

I would have expected type to be NONE since this is read-only.

Fixed.

  1. Could you update tests/zfs-tests/tests/functional/cli_root/zfs_jail/zfs_jail_001_pos.ksh with some simple tests to verify zfs list -o jail ... works?

Sure, especially that existing test provides a working baseline. I've added a separate test not to mix multiple concerns into a single one.

Comment on lines +2104 to +2105
If the jail is renamed, the property will still report its old name from
the time the dataset was mounted.
Copy link
Member

Choose a reason for hiding this comment

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

It makes me wonder if reporting jail ID would be more predictable.

char ds_snapname[ZFS_MAX_DATASET_NAME_LEN];

#ifdef __FreeBSD__
char *ds_jailname;
Copy link
Member

Choose a reason for hiding this comment

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

I am not familiar with zones, but wouldn't this functionality apply there too? Would some ds_zonename without the ifdef be more universal?

struct prison *pr = curthread->td_ucred->cr_prison;
if (pr != &prison0) {
zfsvfs->z_os->os_dsl_dataset->ds_jailname =
kmem_strdup(pr->pr_name);
Copy link
Member

Choose a reason for hiding this comment

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

Again, I am not closely familiar with the zone/jail code in ZFS, but I am not sure the delegation should necessarily be tied to a mounting. I suppose some dataset may not be mounted, but be controlled by the jail. Am I wrong?

@ihoro
Copy link
Contributor Author

ihoro commented Nov 10, 2025

Refreshing the context.

Option1

On the October's monthly call we discussed why it does the trick on mounting/unmounting. The reason is one-to-many relation we have today, i.e. we can call zfs jail <jailname> <dsname> multiple times for the same dataset and delegate it to different jails. All such jails can alter the dataset's properties, etc, but obviously only one of them will have it mounted.
Let's think of current patch as Option1.

Option2

There was a nice discussion with Pawel, where it's found that initial implementation had no intent to support one-to-many. Probably, that's just an outcome from the absence of explicit check against such cases. It seems we could rework the mechanism to one-to-one only. But it's unknown if it's going to break someone's existing logic on top of that, e.g. right now it's okay to change delegation by a secondary zfs jail jail2 ds before doing zfs unjail jail1 ds, and so on.

I've tried to check the things for this option and it does not seem to be trivial. The first task is to have an extra check upon zfs jail to see if the given dataset is already delegated to some jail. The second one is to have quick lookup by dataset name. The variants here:

  • a) Iterate over the list of all jails and iterate over the list of datasets linked to each jail. The obvious O(n) and latest discussions to optimize jail subsystem for big n do not make this variant interesting. While zfs jail is a rare action, doing the same upon zfs list or zfs get does not feel well, considering that it works the way as "get all properties first, then output only requested ones".
  • b1) We can store jail id in dsl_dir_t or dsl_dataset_t structure. And we can cache jail name there as well, to avoid doing jail lookup by jid upon zfs list|get. But the trouble is that usually jailed=on dataset is not mounted by default and it needs zfs jail before zfs mount. So, those structures are get freed before the actual mounting, as nothing references them.
  • b2) Let's keep a reference upon zfs jail and remove it upon zfs unjail? Now we have extra complexity not to forget unref for such cases as: jail is removed before zfs unjail, the pool needs to be exported, the dataset needs to be removed, and so on. And the similar cases to know when to free jail name cache, as the zfs unjail is not the only entry point for that.
  • c) We could have separate structure(s) just for this job. We need something similar to dsl_dir hierarchy to keep it inheritable by sub-datasets, and we want quick lookup by dataset name for property reading.

Option comparison

I've tried to compare these options:

               | Option1                 | Option2
--------------------------------------------------------------------
Patch          | Show which jail mounted | Show the jail the ds is
positioning    | the ds.                 | delegated to by allowing
               |                         | one-to-one relation only.
--------------------------------------------------------------------
Backward       | All the same.           | Now it is limited to one-
compatibility  |                         | to-one relation, which
               |                         | may break specific cases
               |                         | even if explicit one-to-
               |                         | many is not needed.
--------------------------------------------------------------------
Code           | A small and simple      | Bigger change with extra
complexity     | change.                 | structures, logic, locks,
               |                         | etc.
--------------------------------------------------------------------
Extra care     | Even if "zfs unjail" is | Extra care is expected
               | forgotten, zpool-export | to cover specific cases.
               | zfs-destroy and others  | 
               | will need unmounting    | 
               | first, i.e. we are      | 
               | covered to free mem.    | 
--------------------------------------------------------------------
Ready to use?  | Already implemented.    | Not implemented yet.
               |                         | Probably requires extra
               |                         | time for a heads-up
               |                         | notification and
               |                         | discussion.

My intuition for Option1. Probably, listing the jails we delegated to should not be a property and could be provided with something like zfs jails <dsname> where we could allow doing simple iterating over all jails. While using "jail" property only to show the jail which actually mounted the dataset is fine. And we could preserve the existing mechanisms w/o new limits added, who knows maybe it's useful to have one-to-many relation for a specific use case. Like one jail has it mounted, while another can do other stuff with the dataset w/o mounting. Just thinking out loud, I have no actual examples, I'm just working with the fact that it is allowed all these years and Option2 would remove some possibilities.

My intuition for Option2. If one-to-many has no actual use and limiting it to one-to-one does not break existing things then this option covers all the needs by saying whom we delegated to. And we always have mounted prop to see if it's actually mounted. We just need to agree if we want to bring extra code and complexity or find a simpler way for its implementation.

RFC

This is a request for comments and suggestions. Probably, I do not see other possible options. I wanted to present already implemented Option2 to compare the actual code and approaches, but now it feels better to discuss them before diving into extra code complexities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Code Review Needed Ready for review and testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants