1146040Stjr#! /usr/bin/python2.6
2146040Stjr#
3146040Stjr# CDDL HEADER START
4146040Stjr#
5218Sconklin# The contents of this file are subject to the terms of the
6126209Sache# Common Development and Distribution License (the "License").
7146040Stjr# You may not use this file except in compliance with the License.
8146040Stjr#
9146040Stjr# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10218Sconklin# or http://www.opensolaris.org/os/licensing.
11126209Sache# See the License for the specific language governing permissions
12218Sconklin# and limitations under the License.
13126209Sache#
14146040Stjr# When distributing Covered Code, include this CDDL HEADER in each
15218Sconklin# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16146040Stjr# If applicable, add the following below this CDDL HEADER, with the
17146040Stjr# fields enclosed by brackets "[]" replaced with your own identifying
18146040Stjr# information: Portions Copyright [yyyy] [name of copyright owner]
19146040Stjr#
20218Sconklin# CDDL HEADER END
21218Sconklin#
22146040Stjr# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23218Sconklin#
24218Sconklin
25146040Stjr"""Implements the Dataset class, providing methods for manipulating ZFS
26146040Stjrdatasets.  Also implements the Property class, which describes ZFS
27218Sconklinproperties."""
28146040Stjr
29146040Stjrimport zfs.ioctl
30146040Stjrimport zfs.util
31146040Stjrimport errno
32146040Stjr
33146040Stjr_ = zfs.util._
34146040Stjr
35146040Stjrclass Property(object):
36146040Stjr	"""This class represents a ZFS property.  It contains
37146040Stjr	information about the property -- if it's readonly, a number vs
38146040Stjr	string vs index, etc.  Only native properties are represented by
39146040Stjr	this class -- not user properties (eg "user:prop") or userspace
40146040Stjr	properties (eg "userquota@joe")."""
41146040Stjr
42146040Stjr	__slots__ = "name", "number", "type", "default", "attr", "validtypes", \
43146040Stjr	    "values", "colname", "rightalign", "visible", "indextable"
44146040Stjr	__repr__ = zfs.util.default_repr
45146040Stjr
46146040Stjr	def __init__(self, t):
47218Sconklin		"""t is the tuple of information about this property
48218Sconklin		from zfs.ioctl.get_proptable, which should match the
49126209Sache		members of zprop_desc_t (see zfs_prop.h)."""
50126209Sache
51126209Sache		self.name = t[0]
52126209Sache		self.number = t[1]
53126209Sache		self.type = t[2]
54126209Sache		if self.type == "string":
55126209Sache			self.default = t[3]
56126209Sache		else:
57126209Sache			self.default = t[4]
58126209Sache		self.attr = t[5]
59126209Sache		self.validtypes = t[6]
60126209Sache		self.values = t[7]
61126209Sache		self.colname = t[8]
62126209Sache		self.rightalign = t[9]
63126209Sache		self.visible = t[10]
64126209Sache		self.indextable = t[11]
65126209Sache
66126209Sache	def delegatable(self):
67126209Sache		"""Return True if this property can be delegated with
68126209Sache		"zfs allow"."""
69126209Sache		return self.attr != "readonly"
70218Sconklin
71146040Stjrproptable = dict()
72218Sconklinfor name, t in zfs.ioctl.get_proptable().iteritems():
73218Sconklin	proptable[name] = Property(t)
74146040Stjrdel name, t
75146040Stjr
76146040Stjrdef getpropobj(name):
77218Sconklin	"""Return the Property object that is identified by the given
78146040Stjr	name string.  It can be the full name, or the column name."""
79146040Stjr	try:
80146040Stjr		return proptable[name]
81146040Stjr	except KeyError:
82131543Stjr		for p in proptable.itervalues():
83126209Sache			if p.colname and p.colname.lower() == name:
84146040Stjr				return p
85218Sconklin		raise
86146040Stjr
87146040Stjrclass Dataset(object):
88146040Stjr	"""Represents a ZFS dataset (filesystem, snapshot, zvol, clone, etc).
89218Sconklin
90146040Stjr	Generally, this class provides interfaces to the C functions in
91146040Stjr	zfs.ioctl which actually interface with the kernel to manipulate
92146040Stjr	datasets.
93146040Stjr
94146040Stjr	Unless otherwise noted, any method can raise a ZFSError to
95126209Sache	indicate failure."""
96126209Sache
97126209Sache	__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