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