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