#! /usr/bin/python2.6
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
#
"""Implements the Dataset class, providing methods for manipulating ZFS
datasets. Also implements the Property class, which describes ZFS
properties."""
import zfs.ioctl
import zfs.util
import errno
_ = zfs.util._
class Property(object):
"""This class represents a ZFS property. It contains
information about the property -- if it's readonly, a number vs
string vs index, etc. Only native properties are represented by
this class -- not user properties (eg "user:prop") or userspace
properties (eg "userquota@joe")."""
__slots__ = "name", "number", "type", "default", "attr", "validtypes", \
"values", "colname", "rightalign", "visible", "indextable"
__repr__ = zfs.util.default_repr
def __init__(self, t):
"""t is the tuple of information about this property
from zfs.ioctl.get_proptable, which should match the
members of zprop_desc_t (see zfs_prop.h)."""
self.name = t[0]
self.number = t[1]
self.type = t[2]
if self.type == "string":
self.default = t[3]
else:
self.default = t[4]
self.attr = t[5]
self.validtypes = t[6]
self.values = t[7]
self.colname = t[8]
self.rightalign = t[9]
self.visible = t[10]
self.indextable = t[11]
def delegatable(self):
"""Return True if this property can be delegated with
"zfs allow"."""
return self.attr != "readonly"
proptable = dict()
for name, t in zfs.ioctl.get_proptable().iteritems():
proptable[name] = Property(t)
del name, t
def getpropobj(name):
"""Return the Property object that is identified by the given
name string. It can be the full name, or the column name."""
try:
return proptable[name]
except KeyError:
for p in proptable.itervalues():
if p.colname and p.colname.lower() == name:
return p
raise
class Dataset(object):
"""Represents a ZFS dataset (filesystem, snapshot, zvol, clone, etc).
Generally, this class provides interfaces to the C functions in
zfs.ioctl which actually interface with the kernel to manipulate
datasets.
Unless otherwise noted, any method can raise a ZFSError to
indicate failure."""
__slots__ = "name", "__props"
__repr__ = zfs.util.default_repr
def __init__(self, name, props=None,
types=("filesystem", "volume"), snaps=True):
"""Open the named dataset, checking that it exists and
is of the specified type.
name is the string name of this dataset.
props is the property settings dict from zfs.ioctl.next_dataset.
types is an iterable of strings specifying which types
of datasets are permitted. Accepted strings are
"filesystem" and "volume". Defaults to accepting all
types.
snaps is a boolean specifying if snapshots are acceptable.
Raises a ZFSError if the dataset can't be accessed (eg
doesn't exist) or is not of the specified type.
"""
self.name = name
e = zfs.util.ZFSError(errno.EINVAL,
_("cannot open %s") % name,
_("operation not applicable to datasets of this type"))
if "@" in name and not snaps:
raise e
if not props:
props = zfs.ioctl.dataset_props(name)
self.__props = props
if "volume" not in types and self.getprop("type") == 3:
raise e
if "filesystem" not in types and self.getprop("type") == 2:
raise e
def getprop(self, propname):
"""Return the value of the given property for this dataset.
Currently only works for native properties (those with a
Property object.)
Raises KeyError if propname does not specify a native property.
Does not raise ZFSError.
"""
p = getpropobj(propname)
try:
return self.__props[p.name]["value"]
except KeyError:
return p.default
def parent(self):
"""Return a Dataset representing the parent of this one."""
return Dataset(self.name[:self.name.rindex("/")])
def descendents(self):
"""A generator function which iterates over all
descendent Datasets (not including snapshots."""
cookie = 0
while True:
# next_dataset raises StopIteration when done
(name, cookie, props) = \
zfs.ioctl.next_dataset(self.name, False, cookie)
ds = Dataset(name, props)
yield ds
for child in ds.descendents():
yield child
def userspace(self, prop):
"""A generator function which iterates over a
userspace-type property.
prop specifies which property ("userused@",
"userquota@", "groupused@", or "groupquota@").
returns 3-tuple of domain (string), rid (int), and space (int).
"""
d = zfs.ioctl.userspace_many(self.name, prop)
for ((domain, rid), space) in d.iteritems():
yield (domain, rid, space)
def userspace_upgrade(self):
"""Initialize the accounting information for
userused@... and groupused@... properties."""
return zfs.ioctl.userspace_upgrade(self.name)
def set_fsacl(self, un, d):
"""Add to the "zfs allow"-ed permissions on this Dataset.
un is True if the specified permissions should be removed.
d is a dict specifying which permissions to add/remove:
{ "whostr" -> None # remove all perms for this entity
"whostr" -> { "perm" -> None} # add/remove these perms
} """
return zfs.ioctl.set_fsacl(self.name, un, d)
def get_fsacl(self):
"""Get the "zfs allow"-ed permissions on the Dataset.
Return a dict("whostr": { "perm" -> None })."""
return zfs.ioctl.get_fsacl(self.name)
def get_holds(self):
"""Get the user holds on this Dataset.
Return a dict("tag": timestamp)."""
return zfs.ioctl.get_holds(self.name)
def snapshots_fromcmdline(dsnames, recursive):
for dsname in dsnames:
if not "@" in dsname:
raise zfs.util.ZFSError(errno.EINVAL,
_("cannot open %s") % dsname,
_("operation only applies to snapshots"))
try:
ds = Dataset(dsname)
yield ds
except zfs.util.ZFSError, e:
if not recursive or e.errno != errno.ENOENT:
raise
if recursive:
(base, snapname) = dsname.split('@')
parent = Dataset(base)
for child in parent.descendents():
try:
yield Dataset(child.name + "@" +
snapname)
except zfs.util.ZFSError, e:
if e.errno != errno.ENOENT:
raise