xref: /freebsd/cddl/contrib/opensolaris/lib/pyzfs/common/dataset.py (revision 0957b409a90fd597c1e9124cbaf3edd2b488f4ac)
1#! /usr/bin/python2.6
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23#
24
25"""Implements the Dataset class, providing methods for manipulating ZFS
26datasets.  Also implements the Property class, which describes ZFS
27properties."""
28
29import zfs.ioctl
30import zfs.util
31import errno
32
33_ = zfs.util._
34
35class Property(object):
36	"""This class represents a ZFS property.  It contains
37	information about the property -- if it's readonly, a number vs
38	string vs index, etc.  Only native properties are represented by
39	this class -- not user properties (eg "user:prop") or userspace
40	properties (eg "userquota@joe")."""
41
42	__slots__ = "name", "number", "type", "default", "attr", "validtypes", \
43	    "values", "colname", "rightalign", "visible", "indextable"
44	__repr__ = zfs.util.default_repr
45
46	def __init__(self, t):
47		"""t is the tuple of information about this property
48		from zfs.ioctl.get_proptable, which should match the
49		members of zprop_desc_t (see zfs_prop.h)."""
50
51		self.name = t[0]
52		self.number = t[1]
53		self.type = t[2]
54		if self.type == "string":
55			self.default = t[3]
56		else:
57			self.default = t[4]
58		self.attr = t[5]
59		self.validtypes = t[6]
60		self.values = t[7]
61		self.colname = t[8]
62		self.rightalign = t[9]
63		self.visible = t[10]
64		self.indextable = t[11]
65
66	def delegatable(self):
67		"""Return True if this property can be delegated with
68		"zfs allow"."""
69		return self.attr != "readonly"
70
71proptable = dict()
72for name, t in zfs.ioctl.get_proptable().iteritems():
73	proptable[name] = Property(t)
74del name, t
75
76def getpropobj(name):
77	"""Return the Property object that is identified by the given
78	name string.  It can be the full name, or the column name."""
79	try:
80		return proptable[name]
81	except KeyError:
82		for p in proptable.itervalues():
83			if p.colname and p.colname.lower() == name:
84				return p
85		raise
86
87class Dataset(object):
88	"""Represents a ZFS dataset (filesystem, snapshot, zvol, clone, etc).
89
90	Generally, this class provides interfaces to the C functions in
91	zfs.ioctl which actually interface with the kernel to manipulate
92	datasets.
93
94	Unless otherwise noted, any method can raise a ZFSError to
95	indicate failure."""
96
97	__slots__ = "name", "__props"
98	__repr__ = zfs.util.default_repr
99
100	def __init__(self, name, props=None,
101	    types=("filesystem", "volume"), snaps=True):
102		"""Open the named dataset, checking that it exists and
103		is of the specified type.
104
105		name is the string name of this dataset.
106
107		props is the property settings dict from zfs.ioctl.next_dataset.
108
109		types is an iterable of strings specifying which types
110		of datasets are permitted.  Accepted strings are
111		"filesystem" and "volume".  Defaults to accepting all
112		types.
113
114		snaps is a boolean specifying if snapshots are acceptable.
115
116		Raises a ZFSError if the dataset can't be accessed (eg
117		doesn't exist) or is not of the specified type.
118		"""
119
120		self.name = name
121
122		e = zfs.util.ZFSError(errno.EINVAL,
123		    _("cannot open %s") % name,
124		    _("operation not applicable to datasets of this type"))
125		if "@" in name and not snaps:
126			raise e
127		if not props:
128			props = zfs.ioctl.dataset_props(name)
129		self.__props = props
130		if "volume" not in types and self.getprop("type") == 3:
131			raise e
132		if "filesystem" not in types and self.getprop("type") == 2:
133			raise e
134
135	def getprop(self, propname):
136		"""Return the value of the given property for this dataset.
137
138		Currently only works for native properties (those with a
139		Property object.)
140
141		Raises KeyError if propname does not specify a native property.
142		Does not raise ZFSError.
143		"""
144
145		p = getpropobj(propname)
146		try:
147			return self.__props[p.name]["value"]
148		except KeyError:
149			return p.default
150
151	def parent(self):
152		"""Return a Dataset representing the parent of this one."""
153		return Dataset(self.name[:self.name.rindex("/")])
154
155	def descendents(self):
156		"""A generator function which iterates over all
157		descendent Datasets (not including snapshots."""
158
159		cookie = 0
160		while True:
161			# next_dataset raises StopIteration when done
162			(name, cookie, props) = \
163			    zfs.ioctl.next_dataset(self.name, False, cookie)
164			ds = Dataset(name, props)
165			yield ds
166			for child in ds.descendents():
167				yield child
168
169	def userspace(self, prop):
170		"""A generator function which iterates over a
171		userspace-type property.
172
173		prop specifies which property ("userused@",
174		"userquota@", "groupused@", or "groupquota@").
175
176		returns 3-tuple of domain (string), rid (int), and space (int).
177		"""
178
179		d = zfs.ioctl.userspace_many(self.name, prop)
180		for ((domain, rid), space) in d.iteritems():
181			yield (domain, rid, space)
182
183	def userspace_upgrade(self):
184		"""Initialize the accounting information for
185		userused@... and groupused@... properties."""
186		return zfs.ioctl.userspace_upgrade(self.name)
187
188	def set_fsacl(self, un, d):
189		"""Add to the "zfs allow"-ed permissions on this Dataset.
190
191		un is True if the specified permissions should be removed.
192
193		d is a dict specifying which permissions to add/remove:
194		{ "whostr" -> None # remove all perms for this entity
195		  "whostr" -> { "perm" -> None} # add/remove these perms
196		} """
197		return zfs.ioctl.set_fsacl(self.name, un, d)
198
199	def get_fsacl(self):
200		"""Get the "zfs allow"-ed permissions on the Dataset.
201
202		Return a dict("whostr": { "perm" -> None })."""
203
204		return zfs.ioctl.get_fsacl(self.name)
205
206	def get_holds(self):
207		"""Get the user holds on this Dataset.
208
209		Return a dict("tag": timestamp)."""
210
211		return zfs.ioctl.get_holds(self.name)
212
213def snapshots_fromcmdline(dsnames, recursive):
214	for dsname in dsnames:
215		if not "@" in dsname:
216			raise zfs.util.ZFSError(errno.EINVAL,
217			    _("cannot open %s") % dsname,
218			    _("operation only applies to snapshots"))
219		try:
220			ds = Dataset(dsname)
221			yield ds
222		except zfs.util.ZFSError, e:
223			if not recursive or e.errno != errno.ENOENT:
224				raise
225		if recursive:
226			(base, snapname) = dsname.split('@')
227			parent = Dataset(base)
228			for child in parent.descendents():
229				try:
230					yield Dataset(child.name + "@" +
231					    snapname)
232				except zfs.util.ZFSError, e:
233					if e.errno != errno.ENOENT:
234						raise
235