OmniCube Reference Manualoc_policy(3)

oc_policy(3)

Library Functions · OmniCube · August 10, 2026

NAME

oc_policy, get_snap_policy_path, _bkp_priv - resolve an OmniCube snapshot policy file and validate the remote privilege prefix

SYNOPSIS

amp;. /opt/omnicube/lib/common/utils.sh

policy_file=$(get_snap_policy_path policy)
remote_priv=$(_bkp_priv bkppriv)

DESCRIPTION

Snapshot and replication behaviour is not compiled into the backup jobs: it lives in a per-dataset policy file. Each dataset carries the ZFS user property ${PROPPREFIX}:snappolicy, whose value is a policy name; autosnap(8), autosync(8) and autocleansnap(8) turn that name into a path with get_snap_policy_path() and then source the file to obtain RETENTION, BKPUSER, BKPHOST, BKPDS, IS_ENCRYPT, IS_SYNC and BKPPRIV. See default.conf(5) for the file format.

get_snap_policy_path

Prints the path of the policy file for policy on standard output.

The name is normalized first: an empty argument, or the single character - — which is what zfs get -H -o value prints for an unset user property — becomes default. A dataset with no snappolicy property therefore gets the default policy rather than an error, which is what makes the property optional in practice.

Two locations are then tried, in this order:

/var/opt/omnicube/backup/policies/policy.conf

The site-local policy. Preferred if it exists.

/opt/omnicube/backup/policies/policy.conf

The packaged policy, used as the fallback. In a stock installation the only file present here is default.conf, so an unmodified deployment resolves every dataset to /opt/omnicube/backup/policies/default.conf until a site policy is created under /var/opt.

The split is the usual illumos packaging convention: /opt holds what the package delivers and an upgrade may replace, /var/opt holds site configuration that must survive an upgrade. A site policy shadows a packaged policy of the same name, so default itself can be overridden locally without editing a delivered file.

If neither file exists the function reports

Backup policy <policy> does not exist

through error() and returns 1 with nothing on standard output. Callers test the status and skip that dataset:

snap_policy_file=$(get_snap_policy_path ${snap_policy})
[[ $? -ne 0 ]] && continue

_bkp_priv

Echoes bkppriv if it is a plausible command name, and nothing otherwise. It exists because the two classes of remote host in an OmniCube deployment need different privilege handling.

Cluster nodes, listed in the SMF property config/nodes, always run illumos with this same package, so remote privileged commands are hard-coded as pfexec at the call site — in sync_pool.sh(8), manage_zone.sh(8) and create_zone_pool.sh(8).

A backup host, named by BKPHOST in a snapshot policy, is a different animal: it may be Linux, where pfexec(1) does not exist, and it is commonly configured with zfs allow delegation or with a root account instead. Hard-coding pfexec there would break every such deployment, so the prefix is opt-in per policy:

BKPPRIV=pfexec    # illumos backup host, non-root BKPUSER
BKPPRIV=          # (default) Linux host, root user, or
                  # zfs-allow delegation - no prefix

The value is accepted only if it matches

^[a-zA-Z0-9_.-]+$

i.e. a bare command name, so a policy file cannot smuggle arguments or a second command into the remote command line. An empty value returns immediately with no output, which is the normal case. An invalid value is ignored, not fatal: the function prints

utils.sh: invalid BKPPRIV '<p>', ignoring (expected a bare
command name, e.g. pfexec)

on standard error, logs the same through logger -p user.error -t ${logtag} and echoes nothing, so the caller proceeds with an empty prefix and the subsequent unprivileged remote command fails loudly.

The diagnostic deliberately does not use error(). This function's standard output is the value the caller captures with $(), and error() writes both to the log and to mailx(1), whose own standard output would then be captured into the privilege prefix and interpolated straight into a remote command line — inverting the very check that had just failed. Writing to standard error and letting the caller's next failure be the loud signal is the only safe option. See oc_log(3).

Callers append a single space when the result is non-empty, so that the prefix composes with the command that follows and disappears entirely when unset:

RPRIV=$(_bkp_priv "${BKPPRIV}")
[[ -n ${RPRIV} ]] && RPRIV="${RPRIV} "
${_SSH_CMD} "${BKPUSER}@${BKPHOST}" "${RPRIV}zfs recv ${BKPDS}"

RETURN VALUES

Neither function calls exit. Both communicate their result on standard output and must be called with $(), which is safe here precisely because neither maintains shell state.

get_snap_policy_path()

0 after printing a path that exists; 1 after an error() when neither the site nor the packaged file is present, with no output. Note that the status of the command substitution is what callers test, so the $? test must immediately follow the assignment.

_bkp_priv()

0 in every case. Success is indicated by the output, not the status: the caller gets either the validated command name, or an empty string both when bkppriv was empty and when it was rejected.

FILES

/var/opt/omnicube/backup/policies/<policy>.conf

Site policy, tried first. Must be root-owned and not group- or world-writable.

/opt/omnicube/backup/policies/default.conf

Packaged default policy and the fallback for any policy name.

ENVIRONMENT

PATH

_bkp_priv() uses logger(1) for its diagnostic; the callers of both functions use zfs(8) and ssh(1).

Neither function reads any environment variable directly. Both depend on library globals set at source time: logtag for the log tag and, indirectly through error(), the mail settings described in oc_log(3).

EXAMPLES

Example 1: resolving a policy per dataset

snap_policy=$(zfs get -H -o value ${PROPPREFIX}:snappolicy ${DS})
snap_policy_file=$(get_snap_policy_path ${snap_policy})
[[ $? -ne 0 ]] && continue
unset RETENTION BKPUSER BKPHOST BKPDS IS_ENCRYPT IS_SYNC BKPPRIV
source ${snap_policy_file}

Example 2: composing the remote privilege prefix

RPRIV=$(_bkp_priv "${BKPPRIV}")
[[ -n ${RPRIV} ]] && RPRIV="${RPRIV} "
${_SSH_CMD} "${BKPUSER}@${BKPHOST}" \\
    "${RPRIV}zfs destroy ${BKPDS}/${zone}@${snap}"

Example 3: checking which file a policy name resolves to

amp;. /opt/omnicube/lib/common/utils.sh
get_snap_policy_path hourly-offsite

SECURITY

The validation in _bkp_priv() is a typo and copy-paste guard, not a containment boundary. A policy file is sourced as bash, so anything in it already runs locally with the caller's privileges before this function is ever reached. The real boundary is the ownership and mode of /var/opt/omnicube/backup/policies/*.conf, which must be root-owned and neither group- nor world-writable. What the check does buy is that a mistyped or over-specified BKPPRIV cannot become extra words in a command string that is re-parsed by a shell on the backup host.

Because get_snap_policy_path () prefers /var/opt over /opt, write access to the site policy directory is equivalent to control over what the backup jobs execute on both sides of the connection.

SEE ALSO

omnicube_utils(3), oc_log(3), oc_lock(3), oc_runlevel(3), oc_validate(3), oc_ssh(3), default.conf(5), autosnap(8), autosync(8), autocleansnap(8), omnicube(7).

NOTES

get_snap_policy_path() only checks that the file exists. It does not validate its ownership, its mode or its contents; the consuming job is responsible for validating the values it obtains, which is why autosync(8) runs every dataset name from a policy through validate_dataset() — see oc_validate(3).

The policy name comes from a ZFS user property and is interpolated into a path. A name containing a slash or .. would resolve outside the policy directories; nothing in this function prevents that, so write access to the snappolicy property of a dataset should be treated as privileged.

An invalid BKPPRIV is reported once per call, and the callers call _bkp_priv () inside their per-dataset loop, so a single typo in a shared policy produces one log line per dataset processed.

man3/oc_policy.3generated 2026-09-02 05:17 CEST