1219089Spjd#! /usr/bin/python2.6
2209962Smm#
3209962Smm# CDDL HEADER START
4209962Smm#
5209962Smm# The contents of this file are subject to the terms of the
6209962Smm# Common Development and Distribution License (the "License").
7209962Smm# You may not use this file except in compliance with the License.
8209962Smm#
9209962Smm# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10209962Smm# or http://www.opensolaris.org/os/licensing.
11209962Smm# See the License for the specific language governing permissions
12209962Smm# and limitations under the License.
13209962Smm#
14209962Smm# When distributing Covered Code, include this CDDL HEADER in each
15209962Smm# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16209962Smm# If applicable, add the following below this CDDL HEADER, with the
17209962Smm# fields enclosed by brackets "[]" replaced with your own identifying
18209962Smm# information: Portions Copyright [yyyy] [name of copyright owner]
19209962Smm#
20209962Smm# CDDL HEADER END
21209962Smm#
22219089Spjd# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23209962Smm#
24209962Smm
25209962Smm"""Implements the Dataset class, providing methods for manipulating ZFS
26209962Smmdatasets.  Also implements the Property class, which describes ZFS
27209962Smmproperties."""
28209962Smm
29209962Smmimport zfs.ioctl
30209962Smmimport zfs.util
31209962Smmimport errno
32209962Smm
33209962Smm_ = zfs.util._
34209962Smm
35209962Smmclass Property(object):
36209962Smm	"""This class represents a ZFS property.  It contains
37209962Smm	information about the property -- if it's readonly, a number vs
38209962Smm	string vs index, etc.  Only native properties are represented by
39209962Smm	this class -- not user properties (eg "user:prop") or userspace
40209962Smm	properties (eg "userquota@joe")."""
41209962Smm
42209962Smm	__slots__ = "name", "number", "type", "default", "attr", "validtypes", \
43209962Smm	    "values", "colname", "rightalign", "visible", "indextable"
44209962Smm	__repr__ = zfs.util.default_repr
45209962Smm
46209962Smm	def __init__(self, t):
47209962Smm		"""t is the tuple of information about this property
48209962Smm		from zfs.ioctl.get_proptable, which should match the
49209962Smm		members of zprop_desc_t (see zfs_prop.h)."""
50209962Smm
51209962Smm		self.name = t[0]
52209962Smm		self.number = t[1]
53209962Smm		self.type = t[2]
54209962Smm		if self.type == "string":
55209962Smm			self.default = t[3]
56209962Smm		else:
57209962Smm			self.default = t[4]
58209962Smm		self.attr = t[5]
59209962Smm		self.validtypes = t[6]
60209962Smm		self.values = t[7]
61209962Smm		self.colname = t[8]
62209962Smm		self.rightalign = t[9]
63209962Smm		self.visible = t[10]
64209962Smm		self.indextable = t[11]
65209962Smm
66209962Smm	def delegatable(self):
67209962Smm		"""Return True if this property can be delegated with
68209962Smm		"zfs allow"."""
69209962Smm		return self.attr != "readonly"
70209962Smm
71209962Smmproptable = dict()
72209962Smmfor name, t in zfs.ioctl.get_proptable().iteritems():
73209962Smm	proptable[name] = Property(t)
74209962Smmdel name, t
75209962Smm
76209962Smmdef getpropobj(name):
77209962Smm	"""Return the Property object that is identified by the given
78209962Smm	name string.  It can be the full name, or the column name."""
79209962Smm	try:
80209962Smm		return proptable[name]
81209962Smm	except KeyError:
82209962Smm		for p in proptable.itervalues():
83209962Smm			if p.colname and p.colname.lower() == name:
84209962Smm				return p
85209962Smm		raise
86209962Smm
87209962Smmclass Dataset(object):
88209962Smm	"""Represents a ZFS dataset (filesystem, snapshot, zvol, clone, etc).
89209962Smm
90209962Smm	Generally, this class provides interfaces to the C functions in
91209962Smm	zfs.ioctl which actually interface with the kernel to manipulate
92209962Smm	datasets.
93209962Smm
94209962Smm	Unless otherwise noted, any method can raise a ZFSError to
95209962Smm	indicate failure."""
96209962Smm
97209962Smm	__slots__ = "name", "__props"
98209962Smm	__repr__ = zfs.util.default_repr
99209962Smm
100209962Smm	def __init__(self, name, props=None,
101209962Smm	    types=("filesystem", "volume"), snaps=True):
102209962Smm		"""Open the named dataset, checking that it exists and
103209962Smm		is of the specified type.
104209962Smm
105209962Smm		name is the string name of this dataset.
106209962Smm
107209962Smm		props is the property settings dict from zfs.ioctl.next_dataset.
108209962Smm
109209962Smm		types is an iterable of strings specifying which types
110209962Smm		of datasets are permitted.  Accepted strings are
111219089Spjd		"filesystem" and "volume".  Defaults to accepting all
112209962Smm		types.
113209962Smm
114209962Smm		snaps is a boolean specifying if snapshots are acceptable.
115209962Smm
116209962Smm		Raises a ZFSError if the dataset can't be accessed (eg
117209962Smm		doesn't exist) or is not of the specified type.
118209962Smm		"""
119209962Smm
120209962Smm		self.name = name
121209962Smm
122209962Smm		e = zfs.util.ZFSError(errno.EINVAL,
123209962Smm		    _("cannot open %s") % name,
124209962Smm		    _("operation not applicable to datasets of this type"))
125209962Smm		if "@" in name and not snaps:
126209962Smm			raise e
127209962Smm		if not props:
128209962Smm			props = zfs.ioctl.dataset_props(name)
129209962Smm		self.__props = props
130209962Smm		if "volume" not in types and self.getprop("type") == 3:
131209962Smm			raise e
132209962Smm		if "filesystem" not in types and self.getprop("type") == 2:
133209962Smm			raise e
134209962Smm
135209962Smm	def getprop(self, propname):
136209962Smm		"""Return the value of the given property for this dataset.
137209962Smm
138209962Smm		Currently only works for native properties (those with a
139209962Smm		Property object.)
140209962Smm
141209962Smm		Raises KeyError if propname does not specify a native property.
142209962Smm		Does not raise ZFSError.
143209962Smm		"""
144209962Smm
145209962Smm		p = getpropobj(propname)
146209962Smm		try:
147209962Smm			return self.__props[p.name]["value"]
148209962Smm		except KeyError:
149209962Smm			return p.default
150209962Smm
151209962Smm	def parent(self):
152209962Smm		"""Return a Dataset representing the parent of this one."""
153209962Smm		return Dataset(self.name[:self.name.rindex("/")])
154209962Smm
155209962Smm	def descendents(self):
156209962Smm		"""A generator function which iterates over all
157209962Smm		descendent Datasets (not including snapshots."""
158209962Smm
159209962Smm		cookie = 0
160209962Smm		while True:
161209962Smm			# next_dataset raises StopIteration when done
162209962Smm			(name, cookie, props) = \
163209962Smm			    zfs.ioctl.next_dataset(self.name, False, cookie)
164209962Smm			ds = Dataset(name, props)
165209962Smm			yield ds
166209962Smm			for child in ds.descendents():
167209962Smm				yield child
168209962Smm
169209962Smm	def userspace(self, prop):
170209962Smm		"""A generator function which iterates over a
171209962Smm		userspace-type property.
172209962Smm
173209962Smm		prop specifies which property ("userused@",
174209962Smm		"userquota@", "groupused@", or "groupquota@").
175209962Smm
176209962Smm		returns 3-tuple of domain (string), rid (int), and space (int).
177209962Smm		"""
178209962Smm
179209962Smm		d = zfs.ioctl.userspace_many(self.name, prop)
180209962Smm		for ((domain, rid), space) in d.iteritems():
181209962Smm			yield (domain, rid, space)
182209962Smm
183209962Smm	def userspace_upgrade(self):
184209962Smm		"""Initialize the accounting information for
185209962Smm		userused@... and groupused@... properties."""
186209962Smm		return zfs.ioctl.userspace_upgrade(self.name)
187209962Smm
188209962Smm	def set_fsacl(self, un, d):
189209962Smm		"""Add to the "zfs allow"-ed permissions on this Dataset.
190209962Smm
191209962Smm		un is True if the specified permissions should be removed.
192209962Smm
193209962Smm		d is a dict specifying which permissions to add/remove:
194209962Smm		{ "whostr" -> None # remove all perms for this entity
195209962Smm		  "whostr" -> { "perm" -> None} # add/remove these perms
196209962Smm		} """
197209962Smm		return zfs.ioctl.set_fsacl(self.name, un, d)
198209962Smm
199209962Smm	def get_fsacl(self):
200209962Smm		"""Get the "zfs allow"-ed permissions on the Dataset.
201209962Smm
202209962Smm		Return a dict("whostr": { "perm" -> None })."""
203209962Smm
204209962Smm		return zfs.ioctl.get_fsacl(self.name)
205219089Spjd
206219089Spjd	def get_holds(self):
207219089Spjd		"""Get the user holds on this Dataset.
208219089Spjd
209219089Spjd		Return a dict("tag": timestamp)."""
210219089Spjd
211219089Spjd		return zfs.ioctl.get_holds(self.name)
212219089Spjd
213219089Spjddef snapshots_fromcmdline(dsnames, recursive):
214219089Spjd	for dsname in dsnames:
215219089Spjd		if not "@" in dsname:
216219089Spjd			raise zfs.util.ZFSError(errno.EINVAL,
217219089Spjd			    _("cannot open %s") % dsname,
218219089Spjd			    _("operation only applies to snapshots"))
219219089Spjd		try:
220219089Spjd			ds = Dataset(dsname)
221219089Spjd			yield ds
222219089Spjd		except zfs.util.ZFSError, e:
223219089Spjd			if not recursive or e.errno != errno.ENOENT:
224219089Spjd				raise
225219089Spjd		if recursive:
226219089Spjd			(base, snapname) = dsname.split('@')
227219089Spjd			parent = Dataset(base)
228219089Spjd			for child in parent.descendents():
229219089Spjd				try:
230219089Spjd					yield Dataset(child.name + "@" +
231219089Spjd					    snapname)
232219089Spjd				except zfs.util.ZFSError, e:
233219089Spjd					if e.errno != errno.ENOENT:
234219089Spjd						raise
235