]> CyberLeo.Net >> Repos - FreeBSD/stable/8.git/blob - cddl/contrib/opensolaris/lib/pyzfs/common/userspace.py
MFC r209962, r211970-r211972, r212050, r212605, r212611
[FreeBSD/stable/8.git] / cddl / contrib / opensolaris / lib / pyzfs / common / userspace.py
1 #! /usr/bin/python2.4
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 2009 Sun Microsystems, Inc.  All rights reserved.
23 # Use is subject to license terms.
24 #
25
26 """This module implements the "zfs userspace" and "zfs groupspace" subcommands.
27 The only public interface is the zfs.userspace.do_userspace() function."""
28
29 import zfs.util
30 import zfs.ioctl
31 import zfs.dataset
32 import optparse
33 import sys
34 import pwd
35 import grp
36 import errno
37
38 _ = zfs.util._
39
40 # map from property name prefix -> (field name, isgroup)
41 props = {
42     "userused@": ("used", False),
43     "userquota@": ("quota", False),
44     "groupused@": ("used", True),
45     "groupquota@": ("quota", True),
46 }
47
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:
52                 return True
53         if isgroup and "posixgroup" not in options.types and \
54             "smbgroup" not in options.types:
55                 return True
56         if not isgroup and "posixuser" not in options.types and \
57             "smbuser" not in options.types:
58                 return True
59         return False
60
61 def updatemax(d, k, v):
62         d[k] = max(d.get(k, None), v)
63
64 def new_entry(options, isgroup, domain, rid):
65         """Return a dict("field": value) for this domain (string) + rid (int)"""
66
67         if domain:
68                 idstr = "%s-%u" % (domain, rid)
69         else:
70                 idstr = "%u" % rid
71
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)]
78
79         if typename.lower().replace(" ", "") not in options.types:
80                 return None
81
82         v = dict()
83         v["type"] = typename
84
85         # python's getpwuid/getgrgid is confused by ephemeral uids
86         if not options.noname and rid < 1<<31:
87                 try:
88                         v["name"] = mapfunc(idstr)
89                 except KeyError:
90                         pass
91
92         if "name" not in v:
93                 v["name"] = idstr
94                 if not domain:
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
99         v["used"] = "0"
100         v["used.sort"] = 0
101         v["quota"] = "none"
102         v["quota.sort"] = 0
103         return v
104
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)."""
108
109         (domain, rid, value) = elem
110         (field, isgroup) = props[prop]
111
112         if options.translate and domain:
113                 try:
114                         rid = zfs.ioctl.sid_to_id("%s-%u" % (domain, rid),
115                             not isgroup)
116                         domain = None
117                 except KeyError:
118                         pass;
119         key = (isgroup, domain, rid)
120                 
121         try:
122                 v = acct[key]
123         except KeyError:
124                 v = new_entry(options, isgroup, domain, rid)
125                 if not v:
126                         return
127                 acct[key] = v
128
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"]
132
133         if options.parsable:
134                 v[field] = str(value)
135         else:
136                 v[field] = zfs.util.nicenum(value)
137         for k in v.keys():
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]))
141
142 def do_userspace():
143         """Implements the "zfs userspace" and "zfs groupspace" subcommands."""
144
145         def usage(msg=None):
146                 parser.print_help()
147                 if msg:
148                         print
149                         parser.exit("zfs: error: " + msg)
150                 else:
151                         parser.exit()
152
153         if sys.argv[1] == "userspace":
154                 defaulttypes = "posixuser,smbuser"
155         else:
156                 defaulttypes = "posixgroup,smbgroup"
157
158         fields = ("type", "name", "used", "quota")
159         ljustfields = ("type", "name")
160         types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
161
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")
165
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)"))
188
189         (options, args) = parser.parse_args(sys.argv[2:])
190         if len(args) != 1:
191                 usage(_("wrong number of arguments"))
192         dsname = args[0]
193
194         options.fields = options.fields.split(",")
195         for f in options.fields:
196                 if f not in fields:
197                         usage(_("invalid field %s") % f)
198
199         options.types = options.types.split(",")
200         for t in options.types:
201                 if t not in types:
202                         usage(_("invalid type %s") % t)
203
204         if not options.sortfields:
205                 options.sortfields = [("-s", "type"), ("-s", "name")]
206
207         if "all" in options.types:
208                 options.types = types[1:]
209
210         ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
211
212         if ds.getprop("jailed") and zfs.ioctl.isglobalzone():
213                 options.noname = True
214
215         if not ds.getprop("useraccounting"):
216                 print(_("Initializing accounting information on old filesystem, please wait..."))
217                 ds.userspace_upgrade()
218
219         acct = dict()
220         maxfieldlen = dict()
221
222         # gather and process accounting information
223         for prop in props.keys():
224                 if skiptype(options, prop):
225                         continue;
226                 for elem in ds.userspace(prop):
227                         process_one_raw(acct, maxfieldlen, options, prop, elem)
228
229         # print out headers
230         if not options.noheaders:
231                 line = str()
232                 for field in options.fields:
233                         # make sure the field header will fit
234                         updatemax(maxfieldlen, field, len(field))
235
236                         if field in ljustfields:
237                                 fmt = "%-*s  "
238                         else:
239                                 fmt = "%*s  "
240                         line += fmt % (maxfieldlen[field], field.upper())
241                 print(line)
242
243         # custom sorting func
244         def cmpkey(val):
245                 l = list()
246                 for (opt, field) in options.sortfields:
247                         try:
248                                 n = val[field + ".sort"]
249                         except KeyError:
250                                 n = val[field]
251                         if opt == "-S":
252                                 # reverse sorting
253                                 try:
254                                         n = -n
255                                 except TypeError:
256                                         # it's a string; decompose it
257                                         # into an array of integers,
258                                         # each one the negative of that
259                                         # character
260                                         n = [-ord(c) for c in n]
261                         l.append(n)
262                 return l
263
264         # print out data lines
265         for val in sorted(acct.itervalues(), key=cmpkey):
266                 line = str()
267                 for field in options.fields:
268                         if options.noheaders:
269                                 line += val[field]
270                                 line += "\t"
271                         else:
272                                 if field in ljustfields:
273                                         fmt = "%-*s  "
274                                 else:
275                                         fmt = "%*s  "
276                                 line += fmt % (maxfieldlen[field], val[field])
277                 print(line)