9894 Deliver python3 modules
[unleashed.git] / usr / src / lib / pyzfs / common / allow.py
blob91d4834679cdcb9db3d5b9053b7e8e150c693c12
1 #!@PYTHON@
3 # CDDL HEADER START
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.
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.
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]
20 # CDDL HEADER END
22 # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23 # Copyright (c) 2013 by Delphix. All rights reserved.
24 # Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
27 """This module implements the "zfs allow" and "zfs unallow" subcommands.
28 The only public interface is the zfs.allow.do_allow() function."""
30 import zfs.util
31 import zfs.dataset
32 import optparse
33 import sys
34 import pwd
35 import grp
36 import errno
38 _ = zfs.util._
40 class FSPerms(object):
41 """This class represents all the permissions that are set on a
42 particular filesystem (not including those inherited)."""
44 __slots__ = "create", "sets", "local", "descend", "ld"
45 __repr__ = zfs.util.default_repr
47 def __init__(self, raw):
48 """Create a FSPerms based on the dict of raw permissions
49 from zfs.ioctl.get_fsacl()."""
50 # set of perms
51 self.create = set()
53 # below are { "Ntype name": set(perms) }
54 # where N is a number that we just use for sorting,
55 # type is "user", "group", "everyone", or "" (for sets)
56 # name is a user, group, or set name, or "" (for everyone)
57 self.sets = dict()
58 self.local = dict()
59 self.descend = dict()
60 self.ld = dict()
62 # see the comment in dsl_deleg.c for the definition of whokey
63 for whokey in raw.keys():
64 perms = raw[whokey].keys()
65 whotypechr = whokey[0].lower()
66 ws = whokey[3:]
67 if whotypechr == "c":
68 self.create.update(perms)
69 elif whotypechr == "s":
70 nwho = "1" + ws
71 self.sets.setdefault(nwho, set()).update(perms)
72 else:
73 if whotypechr == "u":
74 try:
75 name = pwd.getpwuid(int(ws)).pw_name
76 except KeyError:
77 name = ws
78 nwho = "1user " + name
79 elif whotypechr == "g":
80 try:
81 name = grp.getgrgid(int(ws)).gr_name
82 except KeyError:
83 name = ws
84 nwho = "2group " + name
85 elif whotypechr == "e":
86 nwho = "3everyone"
87 else:
88 raise ValueError(whotypechr)
90 if whokey[1] == "l":
91 d = self.local
92 elif whokey[1] == "d":
93 d = self.descend
94 else:
95 raise ValueError(whokey[1])
97 d.setdefault(nwho, set()).update(perms)
99 # Find perms that are in both local and descend, and
100 # move them to ld.
101 for nwho in self.local:
102 if nwho not in self.descend:
103 continue
104 # note: these are set operations
105 self.ld[nwho] = self.local[nwho] & self.descend[nwho]
106 self.local[nwho] -= self.ld[nwho]
107 self.descend[nwho] -= self.ld[nwho]
109 @staticmethod
110 def __ldstr(d, header):
111 s = ""
112 for (nwho, perms) in sorted(d.items()):
113 # local and descend may have entries where perms
114 # is an empty set, due to consolidating all
115 # permissions into ld
116 if perms:
117 s += "\t%s %s\n" % \
118 (nwho[1:], ",".join(sorted(perms)))
119 if s:
120 s = header + s
121 return s
123 def __str__(self):
124 s = self.__ldstr(self.sets, _("Permission sets:\n"))
126 if self.create:
127 s += _("Create time permissions:\n")
128 s += "\t%s\n" % ",".join(sorted(self.create))
130 s += self.__ldstr(self.local, _("Local permissions:\n"))
131 s += self.__ldstr(self.descend, _("Descendent permissions:\n"))
132 s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n"))
133 return s.rstrip()
135 def args_to_perms(parser, options, who, perms):
136 """Return a dict of raw perms {"whostr" -> {"perm" -> None}}
137 based on the command-line input."""
139 # perms is not set if we are doing a "zfs unallow <who> <fs>" to
140 # remove all of someone's permissions
141 if perms:
142 setperms = dict(((p, None) for p in perms if p[0] == "@"))
143 baseperms = dict(((canonicalized_perm(p), None)
144 for p in perms if p[0] != "@"))
145 else:
146 setperms = None
147 baseperms = None
149 d = dict()
151 def storeperm(typechr, inheritchr, arg):
152 assert typechr in "ugecs"
153 assert inheritchr in "ld-"
155 def mkwhokey(t):
156 return "%c%c$%s" % (t, inheritchr, arg)
158 if baseperms or not perms:
159 d[mkwhokey(typechr)] = baseperms
160 if setperms or not perms:
161 d[mkwhokey(typechr.upper())] = setperms
163 def decodeid(w, toidfunc, fmt):
164 try:
165 return int(w)
166 except ValueError:
167 try:
168 return toidfunc(w)[2]
169 except KeyError:
170 parser.error(fmt % w)
172 if options.set:
173 storeperm("s", "-", who)
174 elif options.create:
175 storeperm("c", "-", "")
176 else:
177 for w in who:
178 if options.user:
179 id = decodeid(w, pwd.getpwnam,
180 _("invalid user %s"))
181 typechr = "u"
182 elif options.group:
183 id = decodeid(w, grp.getgrnam,
184 _("invalid group %s"))
185 typechr = "g"
186 elif w == "everyone":
187 id = ""
188 typechr = "e"
189 else:
190 try:
191 id = pwd.getpwnam(w)[2]
192 typechr = "u"
193 except KeyError:
194 try:
195 id = grp.getgrnam(w)[2]
196 typechr = "g"
197 except KeyError:
198 parser.error(_("invalid user/group %s") % w)
199 if options.local:
200 storeperm(typechr, "l", id)
201 if options.descend:
202 storeperm(typechr, "d", id)
203 return d
205 perms_subcmd = dict(
206 create=_("Must also have the 'mount' ability"),
207 destroy=_("Must also have the 'mount' ability"),
208 snapshot="",
209 rollback="",
210 clone=_("""Must also have the 'create' ability and 'mount'
211 \t\t\t\tability in the origin file system"""),
212 promote=_("""Must also have the 'mount'
213 \t\t\t\tand 'promote' ability in the origin file system"""),
214 rename=_("""Must also have the 'mount' and 'create'
215 \t\t\t\tability in the new parent"""),
216 receive=_("Must also have the 'mount' and 'create' ability"),
217 allow=_("Must also have the permission that is being\n\t\t\t\tallowed"),
218 mount=_("Allows mount/umount of ZFS datasets"),
219 share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"),
220 send="",
221 hold=_("Allows adding a user hold to a snapshot"),
222 release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
223 diff=_("Allows lookup of paths within a dataset,\n\t\t\t\tgiven an object number. Ordinary users need this\n\t\t\t\tin order to use zfs diff"),
224 bookmark="",
227 perms_other = dict(
228 userprop=_("Allows changing any user property"),
229 userquota=_("Allows accessing any userquota@... property"),
230 groupquota=_("Allows accessing any groupquota@... property"),
231 userused=_("Allows reading any userused@... property"),
232 groupused=_("Allows reading any groupused@... property"),
235 def hasset(ds, setname):
236 """Return True if the given setname (string) is defined for this
237 ds (Dataset)."""
238 # It would be nice to cache the result of get_fsacl().
239 for raw in ds.get_fsacl().values():
240 for whokey in raw.keys():
241 if whokey[0].lower() == "s" and whokey[3:] == setname:
242 return True
243 return False
245 def canonicalized_perm(permname):
246 """Return the canonical name (string) for this permission (string).
247 Raises ZFSError if it is not a valid permission."""
248 if permname in perms_subcmd.keys() or permname in perms_other.keys():
249 return permname
250 try:
251 return zfs.dataset.getpropobj(permname).name
252 except KeyError:
253 raise zfs.util.ZFSError(errno.EINVAL, permname,
254 _("invalid permission"))
256 def print_perms():
257 """Print the set of supported permissions."""
258 print(_("\nThe following permissions are supported:\n"))
259 fmt = "%-16s %-14s\t%s"
260 print(fmt % (_("NAME"), _("TYPE"), _("NOTES")))
262 for (name, note) in sorted(perms_subcmd.items()):
263 print(fmt % (name, _("subcommand"), note))
265 for (name, note) in sorted(perms_other.items()):
266 print(fmt % (name, _("other"), note))
268 for (name, prop) in sorted(zfs.dataset.proptable.items()):
269 if prop.visible and prop.delegatable():
270 print(fmt % (name, _("property"), ""))
272 def do_allow():
273 """Implements the "zfs allow" and "zfs unallow" subcommands."""
274 un = (sys.argv[1] == "unallow")
276 def usage(msg=None):
277 parser.print_help()
278 print_perms()
279 if msg:
280 print
281 parser.exit("zfs: error: " + msg)
282 else:
283 parser.exit()
285 if un:
286 u = _("""unallow [-rldug] <"everyone"|user|group>[,...]
287 [<perm|@setname>[,...]] <filesystem|volume>
288 unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume>
289 unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume>
290 unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""")
291 verb = _("remove")
292 sstr = _("undefine permission set")
293 else:
294 u = _("""allow <filesystem|volume>
295 allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...]
296 <filesystem|volume>
297 allow [-ld] -e <perm|@setname>[,...] <filesystem|volume>
298 allow -c <perm|@setname>[,...] <filesystem|volume>
299 allow -s @setname <perm|@setname>[,...] <filesystem|volume>""")
300 verb = _("set")
301 sstr = _("define permission set")
303 parser = optparse.OptionParser(usage=u, prog="zfs")
305 parser.add_option("-l", action="store_true", dest="local",
306 help=_("%s permission locally") % verb)
307 parser.add_option("-d", action="store_true", dest="descend",
308 help=_("%s permission for descendents") % verb)
309 parser.add_option("-u", action="store_true", dest="user",
310 help=_("%s permission for user") % verb)
311 parser.add_option("-g", action="store_true", dest="group",
312 help=_("%s permission for group") % verb)
313 parser.add_option("-e", action="store_true", dest="everyone",
314 help=_("%s permission for everyone") % verb)
315 parser.add_option("-c", action="store_true", dest="create",
316 help=_("%s create time permissions") % verb)
317 parser.add_option("-s", action="store_true", dest="set", help=sstr)
318 if un:
319 parser.add_option("-r", action="store_true", dest="recursive",
320 help=_("remove permissions recursively"))
322 if len(sys.argv) == 3 and not un:
323 # just print the permissions on this fs
325 if sys.argv[2] == "-h":
326 # hack to make "zfs allow -h" work
327 usage()
328 ds = zfs.dataset.Dataset(sys.argv[2], snaps=False)
330 p = dict()
331 for (fs, raw) in ds.get_fsacl().items():
332 p[fs] = FSPerms(raw)
334 for fs in sorted(p.keys(), reverse=True):
335 s = _("---- Permissions on %s ") % fs
336 print(s + "-" * (70-len(s)))
337 print(p[fs])
338 return
340 (options, args) = parser.parse_args(sys.argv[2:])
342 if sum((bool(options.everyone), bool(options.user),
343 bool(options.group))) > 1:
344 parser.error(_("-u, -g, and -e are mutually exclusive"))
346 def mungeargs(expected_len):
347 if un and len(args) == expected_len-1:
348 return (None, args[expected_len-2])
349 elif len(args) == expected_len:
350 return (args[expected_len-2].split(","),
351 args[expected_len-1])
352 else:
353 usage(_("wrong number of parameters"))
355 if options.set:
356 if options.local or options.descend or options.user or \
357 options.group or options.everyone or options.create:
358 parser.error(_("invalid option combined with -s"))
359 if args[0][0] != "@":
360 parser.error(_("invalid set name: missing '@' prefix"))
362 (perms, fsname) = mungeargs(3)
363 who = args[0]
364 elif options.create:
365 if options.local or options.descend or options.user or \
366 options.group or options.everyone or options.set:
367 parser.error(_("invalid option combined with -c"))
369 (perms, fsname) = mungeargs(2)
370 who = None
371 elif options.everyone:
372 if options.user or options.group or \
373 options.create or options.set:
374 parser.error(_("invalid option combined with -e"))
376 (perms, fsname) = mungeargs(2)
377 who = ["everyone"]
378 else:
379 (perms, fsname) = mungeargs(3)
380 who = args[0].split(",")
382 if not options.local and not options.descend:
383 options.local = True
384 options.descend = True
386 d = args_to_perms(parser, options, who, perms)
388 ds = zfs.dataset.Dataset(fsname, snaps=False)
390 if not un and perms:
391 for p in perms:
392 if p[0] == "@" and not hasset(ds, p):
393 parser.error(_("set %s is not defined") % p)
395 ds.set_fsacl(un, d)
396 if un and options.recursive:
397 for child in ds.descendents():
398 child.set_fsacl(un, d)