xref: /illumos-gate/usr/src/lib/pyzfs/common/userspace.py (revision 148434217c040ea38dc844384f6ba68d9b325906)
1*14843421SMatthew Ahrens#! /usr/bin/python2.4
2*14843421SMatthew Ahrens#
3*14843421SMatthew Ahrens# CDDL HEADER START
4*14843421SMatthew Ahrens#
5*14843421SMatthew Ahrens# The contents of this file are subject to the terms of the
6*14843421SMatthew Ahrens# Common Development and Distribution License (the "License").
7*14843421SMatthew Ahrens# You may not use this file except in compliance with the License.
8*14843421SMatthew Ahrens#
9*14843421SMatthew Ahrens# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*14843421SMatthew Ahrens# or http://www.opensolaris.org/os/licensing.
11*14843421SMatthew Ahrens# See the License for the specific language governing permissions
12*14843421SMatthew Ahrens# and limitations under the License.
13*14843421SMatthew Ahrens#
14*14843421SMatthew Ahrens# When distributing Covered Code, include this CDDL HEADER in each
15*14843421SMatthew Ahrens# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*14843421SMatthew Ahrens# If applicable, add the following below this CDDL HEADER, with the
17*14843421SMatthew Ahrens# fields enclosed by brackets "[]" replaced with your own identifying
18*14843421SMatthew Ahrens# information: Portions Copyright [yyyy] [name of copyright owner]
19*14843421SMatthew Ahrens#
20*14843421SMatthew Ahrens# CDDL HEADER END
21*14843421SMatthew Ahrens#
22*14843421SMatthew Ahrens# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23*14843421SMatthew Ahrens# Use is subject to license terms.
24*14843421SMatthew Ahrens#
25*14843421SMatthew Ahrens
26*14843421SMatthew Ahrens"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
27*14843421SMatthew AhrensThe only public interface is the zfs.userspace.do_userspace() function."""
28*14843421SMatthew Ahrens
29*14843421SMatthew Ahrensimport zfs.util
30*14843421SMatthew Ahrensimport zfs.ioctl
31*14843421SMatthew Ahrensimport zfs.dataset
32*14843421SMatthew Ahrensimport optparse
33*14843421SMatthew Ahrensimport sys
34*14843421SMatthew Ahrensimport pwd
35*14843421SMatthew Ahrensimport grp
36*14843421SMatthew Ahrensimport errno
37*14843421SMatthew Ahrens
38*14843421SMatthew Ahrens_ = zfs.util._
39*14843421SMatthew Ahrens
40*14843421SMatthew Ahrens# map from property name prefix -> (field name, isgroup)
41*14843421SMatthew Ahrensprops = {
42*14843421SMatthew Ahrens    "userused@": ("used", False),
43*14843421SMatthew Ahrens    "userquota@": ("quota", False),
44*14843421SMatthew Ahrens    "groupused@": ("used", True),
45*14843421SMatthew Ahrens    "groupquota@": ("quota", True),
46*14843421SMatthew Ahrens}
47*14843421SMatthew Ahrens
48*14843421SMatthew Ahrensdef skiptype(options, prop):
49*14843421SMatthew Ahrens	"""Return True if this property (eg "userquota@") should be skipped."""
50*14843421SMatthew Ahrens	(field, isgroup) = props[prop]
51*14843421SMatthew Ahrens	if field not in options.fields:
52*14843421SMatthew Ahrens		return True
53*14843421SMatthew Ahrens	if isgroup and "posixgroup" not in options.types and \
54*14843421SMatthew Ahrens	    "smbgroup" not in options.types:
55*14843421SMatthew Ahrens		return True
56*14843421SMatthew Ahrens	if not isgroup and "posixuser" not in options.types and \
57*14843421SMatthew Ahrens	    "smbuser" not in options.types:
58*14843421SMatthew Ahrens		return True
59*14843421SMatthew Ahrens	return False
60*14843421SMatthew Ahrens
61*14843421SMatthew Ahrensdef updatemax(d, k, v):
62*14843421SMatthew Ahrens	d[k] = max(d.get(k, None), v)
63*14843421SMatthew Ahrens
64*14843421SMatthew Ahrensdef new_entry(options, isgroup, domain, rid):
65*14843421SMatthew Ahrens	"""Return a dict("field": value) for this domain (string) + rid (int)"""
66*14843421SMatthew Ahrens
67*14843421SMatthew Ahrens	if domain:
68*14843421SMatthew Ahrens		idstr = "%s-%u" % (domain, rid)
69*14843421SMatthew Ahrens	else:
70*14843421SMatthew Ahrens		idstr = "%u" % rid
71*14843421SMatthew Ahrens
72*14843421SMatthew Ahrens	(typename, mapfunc) = {
73*14843421SMatthew Ahrens	    (1, 1): ("SMB Group",   lambda id: zfs.ioctl.sid_to_name(id, 0)),
74*14843421SMatthew Ahrens	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
75*14843421SMatthew Ahrens	    (0, 1): ("SMB User",    lambda id: zfs.ioctl.sid_to_name(id, 1)),
76*14843421SMatthew Ahrens	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
77*14843421SMatthew Ahrens	}[isgroup, bool(domain)]
78*14843421SMatthew Ahrens
79*14843421SMatthew Ahrens	if typename.lower().replace(" ", "") not in options.types:
80*14843421SMatthew Ahrens		return None
81*14843421SMatthew Ahrens
82*14843421SMatthew Ahrens	v = dict()
83*14843421SMatthew Ahrens	v["type"] = typename
84*14843421SMatthew Ahrens
85*14843421SMatthew Ahrens	# python's getpwuid/getgrgid is confused by ephemeral uids
86*14843421SMatthew Ahrens	if not options.noname and rid < 1<<31:
87*14843421SMatthew Ahrens		try:
88*14843421SMatthew Ahrens			v["name"] = mapfunc(idstr)
89*14843421SMatthew Ahrens		except KeyError:
90*14843421SMatthew Ahrens			pass
91*14843421SMatthew Ahrens
92*14843421SMatthew Ahrens	if "name" not in v:
93*14843421SMatthew Ahrens		v["name"] = idstr
94*14843421SMatthew Ahrens		if not domain:
95*14843421SMatthew Ahrens			# it's just a number, so pad it with spaces so
96*14843421SMatthew Ahrens			# that it will sort numerically
97*14843421SMatthew Ahrens			v["name.sort"] = "%20d" % rid
98*14843421SMatthew Ahrens	# fill in default values
99*14843421SMatthew Ahrens	v["used"] = "0"
100*14843421SMatthew Ahrens	v["used.sort"] = 0
101*14843421SMatthew Ahrens	v["quota"] = "none"
102*14843421SMatthew Ahrens	v["quota.sort"] = 0
103*14843421SMatthew Ahrens	return v
104*14843421SMatthew Ahrens
105*14843421SMatthew Ahrensdef process_one_raw(acct, maxfieldlen, options, prop, elem):
106*14843421SMatthew Ahrens	"""Update the acct and maxfieldlen dicts to incorporate the
107*14843421SMatthew Ahrens	information from this elem from Dataset.userspace(prop)."""
108*14843421SMatthew Ahrens
109*14843421SMatthew Ahrens	(domain, rid, value) = elem
110*14843421SMatthew Ahrens	(field, isgroup) = props[prop]
111*14843421SMatthew Ahrens
112*14843421SMatthew Ahrens	if options.translate and domain:
113*14843421SMatthew Ahrens		try:
114*14843421SMatthew Ahrens			rid = zfs.ioctl.sid_to_id("%s-%u" % (domain, rid),
115*14843421SMatthew Ahrens			    not isgroup)
116*14843421SMatthew Ahrens			domain = None
117*14843421SMatthew Ahrens		except KeyError:
118*14843421SMatthew Ahrens			pass;
119*14843421SMatthew Ahrens	key = (isgroup, domain, rid)
120*14843421SMatthew Ahrens
121*14843421SMatthew Ahrens	try:
122*14843421SMatthew Ahrens		v = acct[key]
123*14843421SMatthew Ahrens	except KeyError:
124*14843421SMatthew Ahrens		v = new_entry(options, isgroup, domain, rid)
125*14843421SMatthew Ahrens		if not v:
126*14843421SMatthew Ahrens			return
127*14843421SMatthew Ahrens		acct[key] = v
128*14843421SMatthew Ahrens
129*14843421SMatthew Ahrens	# Add our value to an existing value, which may be present if
130*14843421SMatthew Ahrens	# options.translate is set.
131*14843421SMatthew Ahrens	value = v[field + ".sort"] = value + v[field + ".sort"]
132*14843421SMatthew Ahrens
133*14843421SMatthew Ahrens	if options.parsable:
134*14843421SMatthew Ahrens		v[field] = str(value)
135*14843421SMatthew Ahrens	else:
136*14843421SMatthew Ahrens		v[field] = zfs.util.nicenum(value)
137*14843421SMatthew Ahrens	for k in v.keys():
138*14843421SMatthew Ahrens		# some of the .sort fields are integers, so have no len()
139*14843421SMatthew Ahrens		if isinstance(v[k], str):
140*14843421SMatthew Ahrens			updatemax(maxfieldlen, k, len(v[k]))
141*14843421SMatthew Ahrens
142*14843421SMatthew Ahrensdef do_userspace():
143*14843421SMatthew Ahrens	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
144*14843421SMatthew Ahrens
145*14843421SMatthew Ahrens	def usage(msg=None):
146*14843421SMatthew Ahrens		parser.print_help()
147*14843421SMatthew Ahrens		if msg:
148*14843421SMatthew Ahrens			print
149*14843421SMatthew Ahrens			parser.exit("zfs: error: " + msg)
150*14843421SMatthew Ahrens		else:
151*14843421SMatthew Ahrens			parser.exit()
152*14843421SMatthew Ahrens
153*14843421SMatthew Ahrens	if sys.argv[1] == "userspace":
154*14843421SMatthew Ahrens		defaulttypes = "posixuser,smbuser"
155*14843421SMatthew Ahrens	else:
156*14843421SMatthew Ahrens		defaulttypes = "posixgroup,smbgroup"
157*14843421SMatthew Ahrens
158*14843421SMatthew Ahrens	fields = ("type", "name", "used", "quota")
159*14843421SMatthew Ahrens	ljustfields = ("type", "name")
160*14843421SMatthew Ahrens	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
161*14843421SMatthew Ahrens
162*14843421SMatthew Ahrens	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
163*14843421SMatthew Ahrens	u += _("    [-t type[,...]] <filesystem|snapshot>")
164*14843421SMatthew Ahrens	parser = optparse.OptionParser(usage=u, prog="zfs")
165*14843421SMatthew Ahrens
166*14843421SMatthew Ahrens	parser.add_option("-n", action="store_true", dest="noname",
167*14843421SMatthew Ahrens	    help=_("Print numeric ID instead of user/group name"))
168*14843421SMatthew Ahrens	parser.add_option("-i", action="store_true", dest="translate",
169*14843421SMatthew Ahrens	    help=_("translate SID to posix (possibly ephemeral) ID"))
170*14843421SMatthew Ahrens	parser.add_option("-H", action="store_true", dest="noheaders",
171*14843421SMatthew Ahrens	    help=_("no headers, tab delimited output"))
172*14843421SMatthew Ahrens	parser.add_option("-p", action="store_true", dest="parsable",
173*14843421SMatthew Ahrens	    help=_("exact (parsable) numeric output"))
174*14843421SMatthew Ahrens	parser.add_option("-o", dest="fields", metavar="field[,...]",
175*14843421SMatthew Ahrens	    default="type,name,used,quota",
176*14843421SMatthew Ahrens	    help=_("print only these fields (eg type,name,used,quota)"))
177*14843421SMatthew Ahrens	parser.add_option("-s", dest="sortfields", metavar="field",
178*14843421SMatthew Ahrens	    type="choice", choices=fields, default=list(),
179*14843421SMatthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
180*14843421SMatthew Ahrens	    help=_("sort field"))
181*14843421SMatthew Ahrens	parser.add_option("-S", dest="sortfields", metavar="field",
182*14843421SMatthew Ahrens	    type="choice", choices=fields, #-s sets the default
183*14843421SMatthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
184*14843421SMatthew Ahrens	    help=_("reverse sort field"))
185*14843421SMatthew Ahrens	parser.add_option("-t", dest="types", metavar="type[,...]",
186*14843421SMatthew Ahrens	    default=defaulttypes,
187*14843421SMatthew Ahrens	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
188*14843421SMatthew Ahrens
189*14843421SMatthew Ahrens	(options, args) = parser.parse_args(sys.argv[2:])
190*14843421SMatthew Ahrens	if len(args) != 1:
191*14843421SMatthew Ahrens		usage(_("wrong number of arguments"))
192*14843421SMatthew Ahrens	dsname = args[0]
193*14843421SMatthew Ahrens
194*14843421SMatthew Ahrens	options.fields = options.fields.split(",")
195*14843421SMatthew Ahrens	for f in options.fields:
196*14843421SMatthew Ahrens		if f not in fields:
197*14843421SMatthew Ahrens			usage(_("invalid field %s") % f)
198*14843421SMatthew Ahrens
199*14843421SMatthew Ahrens	options.types = options.types.split(",")
200*14843421SMatthew Ahrens	for t in options.types:
201*14843421SMatthew Ahrens		if t not in types:
202*14843421SMatthew Ahrens			usage(_("invalid type %s") % t)
203*14843421SMatthew Ahrens
204*14843421SMatthew Ahrens	if not options.sortfields:
205*14843421SMatthew Ahrens		options.sortfields = [("-s", "type"), ("-s", "name")]
206*14843421SMatthew Ahrens
207*14843421SMatthew Ahrens	if "all" in options.types:
208*14843421SMatthew Ahrens		options.types = types[1:]
209*14843421SMatthew Ahrens
210*14843421SMatthew Ahrens	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
211*14843421SMatthew Ahrens
212*14843421SMatthew Ahrens	if ds.getprop("zoned") and zfs.ioctl.isglobalzone():
213*14843421SMatthew Ahrens		options.noname = True
214*14843421SMatthew Ahrens
215*14843421SMatthew Ahrens	if not ds.getprop("useraccounting"):
216*14843421SMatthew Ahrens		print(_("Initializing accounting information on old filesystem, please wait..."))
217*14843421SMatthew Ahrens		ds.userspace_upgrade()
218*14843421SMatthew Ahrens
219*14843421SMatthew Ahrens	acct = dict()
220*14843421SMatthew Ahrens	maxfieldlen = dict()
221*14843421SMatthew Ahrens
222*14843421SMatthew Ahrens	# gather and process accounting information
223*14843421SMatthew Ahrens	for prop in props.keys():
224*14843421SMatthew Ahrens		if skiptype(options, prop):
225*14843421SMatthew Ahrens			continue;
226*14843421SMatthew Ahrens		for elem in ds.userspace(prop):
227*14843421SMatthew Ahrens			process_one_raw(acct, maxfieldlen, options, prop, elem)
228*14843421SMatthew Ahrens
229*14843421SMatthew Ahrens	# print out headers
230*14843421SMatthew Ahrens	if not options.noheaders:
231*14843421SMatthew Ahrens		line = str()
232*14843421SMatthew Ahrens		for field in options.fields:
233*14843421SMatthew Ahrens			# make sure the field header will fit
234*14843421SMatthew Ahrens			updatemax(maxfieldlen, field, len(field))
235*14843421SMatthew Ahrens
236*14843421SMatthew Ahrens			if field in ljustfields:
237*14843421SMatthew Ahrens				fmt = "%-*s  "
238*14843421SMatthew Ahrens			else:
239*14843421SMatthew Ahrens				fmt = "%*s  "
240*14843421SMatthew Ahrens			line += fmt % (maxfieldlen[field], field.upper())
241*14843421SMatthew Ahrens		print(line)
242*14843421SMatthew Ahrens
243*14843421SMatthew Ahrens	# custom sorting func
244*14843421SMatthew Ahrens	def cmpkey(val):
245*14843421SMatthew Ahrens		l = list()
246*14843421SMatthew Ahrens		for (opt, field) in options.sortfields:
247*14843421SMatthew Ahrens			try:
248*14843421SMatthew Ahrens				n = val[field + ".sort"]
249*14843421SMatthew Ahrens			except KeyError:
250*14843421SMatthew Ahrens				n = val[field]
251*14843421SMatthew Ahrens			if opt == "-S":
252*14843421SMatthew Ahrens				# reverse sorting
253*14843421SMatthew Ahrens				try:
254*14843421SMatthew Ahrens					n = -n
255*14843421SMatthew Ahrens				except TypeError:
256*14843421SMatthew Ahrens					# it's a string; decompose it
257*14843421SMatthew Ahrens					# into an array of integers,
258*14843421SMatthew Ahrens					# each one the negative of that
259*14843421SMatthew Ahrens					# character
260*14843421SMatthew Ahrens					n = [-ord(c) for c in n]
261*14843421SMatthew Ahrens			l.append(n)
262*14843421SMatthew Ahrens		return l
263*14843421SMatthew Ahrens
264*14843421SMatthew Ahrens	# print out data lines
265*14843421SMatthew Ahrens	for val in sorted(acct.itervalues(), key=cmpkey):
266*14843421SMatthew Ahrens		line = str()
267*14843421SMatthew Ahrens		for field in options.fields:
268*14843421SMatthew Ahrens			if options.noheaders:
269*14843421SMatthew Ahrens				line += val[field]
270*14843421SMatthew Ahrens				line += "\t"
271*14843421SMatthew Ahrens			else:
272*14843421SMatthew Ahrens				if field in ljustfields:
273*14843421SMatthew Ahrens					fmt = "%-*s  "
274*14843421SMatthew Ahrens				else:
275*14843421SMatthew Ahrens					fmt = "%*s  "
276*14843421SMatthew Ahrens				line += fmt % (maxfieldlen[field], val[field])
277*14843421SMatthew Ahrens		print(line)
278