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]
22 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 # Use is subject to license terms.
26 """This module implements the "zfs userspace" and "zfs groupspace" subcommands.
27 The only public interface is the zfs.userspace.do_userspace() function."""
40 # map from property name prefix -> (field name, isgroup)
42 "userused@": ("used", False),
43 "userquota@": ("quota", False),
44 "groupused@": ("used", True),
45 "groupquota@": ("quota", True),
48 def skiptype(options, prop):
49 """Return True if this property (eg "userquota@") should be skipped."""
50 (field, isgroup) = props[prop]
51 if field not in options.fields:
53 if isgroup and "posixgroup" not in options.types and \
54 "smbgroup" not in options.types:
56 if not isgroup and "posixuser" not in options.types and \
57 "smbuser" not in options.types:
61 def updatemax(d, k, v):
62 d[k] = max(d.get(k, None), v)
64 def new_entry(options, isgroup, domain, rid):
65 """Return a dict("field": value) for this domain (string) + rid (int)"""
68 idstr = "%s-%u" % (domain, rid)
72 (typename, mapfunc) = {
73 (1, 1): ("SMB Group", lambda id: zfs.ioctl.sid_to_name(id, 0)),
74 (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
75 (0, 1): ("SMB User", lambda id: zfs.ioctl.sid_to_name(id, 1)),
76 (0, 0): ("POSIX User", lambda id: pwd.getpwuid(int(id)).pw_name)
77 }[isgroup, bool(domain)]
79 if typename.lower().replace(" ", "") not in options.types:
85 # python's getpwuid/getgrgid is confused by ephemeral uids
86 if not options.noname and rid < 1<<31:
88 v["name"] = mapfunc(idstr)
95 # it's just a number, so pad it with spaces so
96 # that it will sort numerically
97 v["name.sort"] = "%20d" % rid
98 # fill in default values
105 def process_one_raw(acct, maxfieldlen, options, prop, elem):
106 """Update the acct and maxfieldlen dicts to incorporate the
107 information from this elem from Dataset.userspace(prop)."""
109 (domain, rid, value) = elem
110 (field, isgroup) = props[prop]
112 if options.translate and domain:
114 rid = zfs.ioctl.sid_to_id("%s-%u" % (domain, rid),
119 key = (isgroup, domain, rid)
124 v = new_entry(options, isgroup, domain, rid)
129 # Add our value to an existing value, which may be present if
130 # options.translate is set.
131 value = v[field + ".sort"] = value + v[field + ".sort"]
134 v[field] = str(value)
136 v[field] = zfs.util.nicenum(value)
138 # some of the .sort fields are integers, so have no len()
139 if isinstance(v[k], str):
140 updatemax(maxfieldlen, k, len(v[k]))
143 """Implements the "zfs userspace" and "zfs groupspace" subcommands."""
149 parser.exit("zfs: error: " + msg)
153 if sys.argv[1] == "userspace":
154 defaulttypes = "posixuser,smbuser"
156 defaulttypes = "posixgroup,smbgroup"
158 fields = ("type", "name", "used", "quota")
159 ljustfields = ("type", "name")
160 types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
162 u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
163 u += _(" [-t type[,...]] <filesystem|snapshot>")
164 parser = optparse.OptionParser(usage=u, prog="zfs")
166 parser.add_option("-n", action="store_true", dest="noname",
167 help=_("Print numeric ID instead of user/group name"))
168 parser.add_option("-i", action="store_true", dest="translate",
169 help=_("translate SID to posix (possibly ephemeral) ID"))
170 parser.add_option("-H", action="store_true", dest="noheaders",
171 help=_("no headers, tab delimited output"))
172 parser.add_option("-p", action="store_true", dest="parsable",
173 help=_("exact (parsable) numeric output"))
174 parser.add_option("-o", dest="fields", metavar="field[,...]",
175 default="type,name,used,quota",
176 help=_("print only these fields (eg type,name,used,quota)"))
177 parser.add_option("-s", dest="sortfields", metavar="field",
178 type="choice", choices=fields, default=list(),
179 action="callback", callback=zfs.util.append_with_opt,
180 help=_("sort field"))
181 parser.add_option("-S", dest="sortfields", metavar="field",
182 type="choice", choices=fields, #-s sets the default
183 action="callback", callback=zfs.util.append_with_opt,
184 help=_("reverse sort field"))
185 parser.add_option("-t", dest="types", metavar="type[,...]",
186 default=defaulttypes,
187 help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
189 (options, args) = parser.parse_args(sys.argv[2:])
191 usage(_("wrong number of arguments"))
194 options.fields = options.fields.split(",")
195 for f in options.fields:
197 usage(_("invalid field %s") % f)
199 options.types = options.types.split(",")
200 for t in options.types:
202 usage(_("invalid type %s") % t)
204 if not options.sortfields:
205 options.sortfields = [("-s", "type"), ("-s", "name")]
207 if "all" in options.types:
208 options.types = types[1:]
210 ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
212 if ds.getprop("jailed") and zfs.ioctl.isglobalzone():
213 options.noname = True
215 if not ds.getprop("useraccounting"):
216 print(_("Initializing accounting information on old filesystem, please wait..."))
217 ds.userspace_upgrade()
222 # gather and process accounting information
223 for prop in props.keys():
224 if skiptype(options, prop):
226 for elem in ds.userspace(prop):
227 process_one_raw(acct, maxfieldlen, options, prop, elem)
230 if not options.noheaders:
232 for field in options.fields:
233 # make sure the field header will fit
234 updatemax(maxfieldlen, field, len(field))
236 if field in ljustfields:
240 line += fmt % (maxfieldlen[field], field.upper())
243 # custom sorting func
246 for (opt, field) in options.sortfields:
248 n = val[field + ".sort"]
256 # it's a string; decompose it
257 # into an array of integers,
258 # each one the negative of that
260 n = [-ord(c) for c in n]
264 # print out data lines
265 for val in sorted(acct.itervalues(), key=cmpkey):
267 for field in options.fields:
268 if options.noheaders:
272 if field in ljustfields:
276 line += fmt % (maxfieldlen[field], val[field])