]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - cddl/contrib/opensolaris/lib/pyzfs/common/userspace.py
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / cddl / contrib / opensolaris / lib / pyzfs / common / userspace.py
1 #! /usr/bin/python2.6
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 #
24
25 """This module implements the "zfs userspace" and "zfs groupspace" subcommands.
26 The only public interface is the zfs.userspace.do_userspace() function."""
27
28 import optparse
29 import sys
30 import pwd
31 import grp
32 import errno
33 import solaris.misc
34 import zfs.util
35 import zfs.ioctl
36 import zfs.dataset
37 import zfs.table
38
39 _ = zfs.util._
40
41 # map from property name prefix -> (field name, isgroup)
42 props = {
43     "userused@": ("used", False),
44     "userquota@": ("quota", False),
45     "groupused@": ("used", True),
46     "groupquota@": ("quota", True),
47 }
48
49 def skiptype(options, prop):
50         """Return True if this property (eg "userquota@") should be skipped."""
51         (field, isgroup) = props[prop]
52         if field not in options.fields:
53                 return True
54         if isgroup and "posixgroup" not in options.types and \
55             "smbgroup" not in options.types:
56                 return True
57         if not isgroup and "posixuser" not in options.types and \
58             "smbuser" not in options.types:
59                 return True
60         return False
61
62 def new_entry(options, isgroup, domain, rid):
63         """Return a dict("field": value) for this domain (string) + rid (int)"""
64
65         if domain:
66                 idstr = "%s-%u" % (domain, rid)
67         else:
68                 idstr = "%u" % rid
69
70         (typename, mapfunc) = {
71             (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
72             (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
73             (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
74             (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
75         }[isgroup, bool(domain)]
76
77         if typename.lower().replace(" ", "") not in options.types:
78                 return None
79
80         v = dict()
81         v["type"] = typename
82
83         # python's getpwuid/getgrgid is confused by ephemeral uids
84         if not options.noname and rid < 1<<31:
85                 try:
86                         v["name"] = mapfunc(idstr)
87                 except KeyError:
88                         pass
89
90         if "name" not in v:
91                 v["name"] = idstr
92                 if not domain:
93                         # it's just a number, so pad it with spaces so
94                         # that it will sort numerically
95                         v["name.sort"] = "%20d" % rid
96         # fill in default values
97         v["used"] = "0"
98         v["used.sort"] = 0
99         v["quota"] = "none"
100         v["quota.sort"] = 0
101         return v
102
103 def process_one_raw(acct, options, prop, elem):
104         """Update the acct dict to incorporate the
105         information from this elem from Dataset.userspace(prop)."""
106
107         (domain, rid, value) = elem
108         (field, isgroup) = props[prop]
109
110         if options.translate and domain:
111                 try:
112                         rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
113                             not isgroup)
114                         domain = None
115                 except KeyError:
116                         pass;
117         key = (isgroup, domain, rid)
118                 
119         try:
120                 v = acct[key]
121         except KeyError:
122                 v = new_entry(options, isgroup, domain, rid)
123                 if not v:
124                         return
125                 acct[key] = v
126
127         # Add our value to an existing value, which may be present if
128         # options.translate is set.
129         value = v[field + ".sort"] = value + v[field + ".sort"]
130
131         if options.parsable:
132                 v[field] = str(value)
133         else:
134                 v[field] = zfs.util.nicenum(value)
135
136 def do_userspace():
137         """Implements the "zfs userspace" and "zfs groupspace" subcommands."""
138
139         def usage(msg=None):
140                 parser.print_help()
141                 if msg:
142                         print
143                         parser.exit("zfs: error: " + msg)
144                 else:
145                         parser.exit()
146
147         if sys.argv[1] == "userspace":
148                 defaulttypes = "posixuser,smbuser"
149         else:
150                 defaulttypes = "posixgroup,smbgroup"
151
152         fields = ("type", "name", "used", "quota")
153         rjustfields = ("used", "quota")
154         types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
155
156         u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
157         u += _("    [-t type[,...]] <filesystem|snapshot>")
158         parser = optparse.OptionParser(usage=u, prog="zfs")
159
160         parser.add_option("-n", action="store_true", dest="noname",
161             help=_("Print numeric ID instead of user/group name"))
162         parser.add_option("-i", action="store_true", dest="translate",
163             help=_("translate SID to posix (possibly ephemeral) ID"))
164         parser.add_option("-H", action="store_true", dest="noheaders",
165             help=_("no headers, tab delimited output"))
166         parser.add_option("-p", action="store_true", dest="parsable",
167             help=_("exact (parsable) numeric output"))
168         parser.add_option("-o", dest="fields", metavar="field[,...]",
169             default="type,name,used,quota",
170             help=_("print only these fields (eg type,name,used,quota)"))
171         parser.add_option("-s", dest="sortfields", metavar="field",
172             type="choice", choices=fields, default=list(),
173             action="callback", callback=zfs.util.append_with_opt,
174             help=_("sort field"))
175         parser.add_option("-S", dest="sortfields", metavar="field",
176             type="choice", choices=fields, #-s sets the default
177             action="callback", callback=zfs.util.append_with_opt,
178             help=_("reverse sort field"))
179         parser.add_option("-t", dest="types", metavar="type[,...]",
180             default=defaulttypes,
181             help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
182
183         (options, args) = parser.parse_args(sys.argv[2:])
184         if len(args) != 1:
185                 usage(_("wrong number of arguments"))
186         dsname = args[0]
187
188         options.fields = options.fields.split(",")
189         for f in options.fields:
190                 if f not in fields:
191                         usage(_("invalid field %s") % f)
192
193         options.types = options.types.split(",")
194         for t in options.types:
195                 if t not in types:
196                         usage(_("invalid type %s") % t)
197
198         if not options.sortfields:
199                 options.sortfields = [("-s", "type"), ("-s", "name")]
200
201         if "all" in options.types:
202                 options.types = types[1:]
203
204         ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
205
206         if ds.getprop("jailed") and solaris.misc.isglobalzone():
207                 options.noname = True
208
209         if not ds.getprop("useraccounting"):
210                 print(_("Initializing accounting information on old filesystem, please wait..."))
211                 ds.userspace_upgrade()
212
213         # gather and process accounting information
214         # Due to -i, we need to keep a dict, so we can potentially add
215         # together the posix ID and SID's usage.  Grr.
216         acct = dict()
217         for prop in props.keys():
218                 if skiptype(options, prop):
219                         continue;
220                 for elem in ds.userspace(prop):
221                         process_one_raw(acct, options, prop, elem)
222
223         def cmpkey(val):
224                 l = list()
225                 for (opt, field) in options.sortfields:
226                         try:
227                                 n = val[field + ".sort"]
228                         except KeyError:
229                                 n = val[field]
230                         if opt == "-S":
231                                 # reverse sorting
232                                 try:
233                                         n = -n
234                                 except TypeError:
235                                         # it's a string; decompose it
236                                         # into an array of integers,
237                                         # each one the negative of that
238                                         # character
239                                         n = [-ord(c) for c in n]
240                         l.append(n)
241                 return l
242
243         t = zfs.table.Table(options.fields, rjustfields)
244         for val in acct.itervalues():
245                 t.addline(cmpkey(val), val)
246         t.printme(not options.noheaders)