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