xref: /freebsd-src/cddl/contrib/opensolaris/lib/pyzfs/common/userspace.py (revision 10b9d77bf1ccf2f3affafa6261692cb92cf7e992)
1*10b9d77bSPawel Jakub Dawidek#! /usr/bin/python2.6
28fc25799SMartin Matuska#
38fc25799SMartin Matuska# CDDL HEADER START
48fc25799SMartin Matuska#
58fc25799SMartin Matuska# The contents of this file are subject to the terms of the
68fc25799SMartin Matuska# Common Development and Distribution License (the "License").
78fc25799SMartin Matuska# You may not use this file except in compliance with the License.
88fc25799SMartin Matuska#
98fc25799SMartin Matuska# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
108fc25799SMartin Matuska# or http://www.opensolaris.org/os/licensing.
118fc25799SMartin Matuska# See the License for the specific language governing permissions
128fc25799SMartin Matuska# and limitations under the License.
138fc25799SMartin Matuska#
148fc25799SMartin Matuska# When distributing Covered Code, include this CDDL HEADER in each
158fc25799SMartin Matuska# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
168fc25799SMartin Matuska# If applicable, add the following below this CDDL HEADER, with the
178fc25799SMartin Matuska# fields enclosed by brackets "[]" replaced with your own identifying
188fc25799SMartin Matuska# information: Portions Copyright [yyyy] [name of copyright owner]
198fc25799SMartin Matuska#
208fc25799SMartin Matuska# CDDL HEADER END
218fc25799SMartin Matuska#
22*10b9d77bSPawel Jakub Dawidek# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
238fc25799SMartin Matuska#
248fc25799SMartin Matuska
258fc25799SMartin Matuska"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
268fc25799SMartin MatuskaThe only public interface is the zfs.userspace.do_userspace() function."""
278fc25799SMartin Matuska
288fc25799SMartin Matuskaimport optparse
298fc25799SMartin Matuskaimport sys
308fc25799SMartin Matuskaimport pwd
318fc25799SMartin Matuskaimport grp
328fc25799SMartin Matuskaimport errno
33*10b9d77bSPawel Jakub Dawidekimport solaris.misc
34*10b9d77bSPawel Jakub Dawidekimport zfs.util
35*10b9d77bSPawel Jakub Dawidekimport zfs.ioctl
36*10b9d77bSPawel Jakub Dawidekimport zfs.dataset
37*10b9d77bSPawel Jakub Dawidekimport zfs.table
388fc25799SMartin Matuska
398fc25799SMartin Matuska_ = zfs.util._
408fc25799SMartin Matuska
418fc25799SMartin Matuska# map from property name prefix -> (field name, isgroup)
428fc25799SMartin Matuskaprops = {
438fc25799SMartin Matuska    "userused@": ("used", False),
448fc25799SMartin Matuska    "userquota@": ("quota", False),
458fc25799SMartin Matuska    "groupused@": ("used", True),
468fc25799SMartin Matuska    "groupquota@": ("quota", True),
478fc25799SMartin Matuska}
488fc25799SMartin Matuska
498fc25799SMartin Matuskadef skiptype(options, prop):
508fc25799SMartin Matuska	"""Return True if this property (eg "userquota@") should be skipped."""
518fc25799SMartin Matuska	(field, isgroup) = props[prop]
528fc25799SMartin Matuska	if field not in options.fields:
538fc25799SMartin Matuska		return True
548fc25799SMartin Matuska	if isgroup and "posixgroup" not in options.types and \
558fc25799SMartin Matuska	    "smbgroup" not in options.types:
568fc25799SMartin Matuska		return True
578fc25799SMartin Matuska	if not isgroup and "posixuser" not in options.types and \
588fc25799SMartin Matuska	    "smbuser" not in options.types:
598fc25799SMartin Matuska		return True
608fc25799SMartin Matuska	return False
618fc25799SMartin Matuska
628fc25799SMartin Matuskadef new_entry(options, isgroup, domain, rid):
638fc25799SMartin Matuska	"""Return a dict("field": value) for this domain (string) + rid (int)"""
648fc25799SMartin Matuska
658fc25799SMartin Matuska	if domain:
668fc25799SMartin Matuska		idstr = "%s-%u" % (domain, rid)
678fc25799SMartin Matuska	else:
688fc25799SMartin Matuska		idstr = "%u" % rid
698fc25799SMartin Matuska
708fc25799SMartin Matuska	(typename, mapfunc) = {
71*10b9d77bSPawel Jakub Dawidek	    (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
728fc25799SMartin Matuska	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
73*10b9d77bSPawel Jakub Dawidek	    (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
748fc25799SMartin Matuska	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
758fc25799SMartin Matuska	}[isgroup, bool(domain)]
768fc25799SMartin Matuska
778fc25799SMartin Matuska	if typename.lower().replace(" ", "") not in options.types:
788fc25799SMartin Matuska		return None
798fc25799SMartin Matuska
808fc25799SMartin Matuska	v = dict()
818fc25799SMartin Matuska	v["type"] = typename
828fc25799SMartin Matuska
838fc25799SMartin Matuska	# python's getpwuid/getgrgid is confused by ephemeral uids
848fc25799SMartin Matuska	if not options.noname and rid < 1<<31:
858fc25799SMartin Matuska		try:
868fc25799SMartin Matuska			v["name"] = mapfunc(idstr)
878fc25799SMartin Matuska		except KeyError:
888fc25799SMartin Matuska			pass
898fc25799SMartin Matuska
908fc25799SMartin Matuska	if "name" not in v:
918fc25799SMartin Matuska		v["name"] = idstr
928fc25799SMartin Matuska		if not domain:
938fc25799SMartin Matuska			# it's just a number, so pad it with spaces so
948fc25799SMartin Matuska			# that it will sort numerically
958fc25799SMartin Matuska			v["name.sort"] = "%20d" % rid
968fc25799SMartin Matuska	# fill in default values
978fc25799SMartin Matuska	v["used"] = "0"
988fc25799SMartin Matuska	v["used.sort"] = 0
998fc25799SMartin Matuska	v["quota"] = "none"
1008fc25799SMartin Matuska	v["quota.sort"] = 0
1018fc25799SMartin Matuska	return v
1028fc25799SMartin Matuska
103*10b9d77bSPawel Jakub Dawidekdef process_one_raw(acct, options, prop, elem):
104*10b9d77bSPawel Jakub Dawidek	"""Update the acct dict to incorporate the
1058fc25799SMartin Matuska	information from this elem from Dataset.userspace(prop)."""
1068fc25799SMartin Matuska
1078fc25799SMartin Matuska	(domain, rid, value) = elem
1088fc25799SMartin Matuska	(field, isgroup) = props[prop]
1098fc25799SMartin Matuska
1108fc25799SMartin Matuska	if options.translate and domain:
1118fc25799SMartin Matuska		try:
112*10b9d77bSPawel Jakub Dawidek			rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
1138fc25799SMartin Matuska			    not isgroup)
1148fc25799SMartin Matuska			domain = None
1158fc25799SMartin Matuska		except KeyError:
1168fc25799SMartin Matuska			pass;
1178fc25799SMartin Matuska	key = (isgroup, domain, rid)
1188fc25799SMartin Matuska
1198fc25799SMartin Matuska	try:
1208fc25799SMartin Matuska		v = acct[key]
1218fc25799SMartin Matuska	except KeyError:
1228fc25799SMartin Matuska		v = new_entry(options, isgroup, domain, rid)
1238fc25799SMartin Matuska		if not v:
1248fc25799SMartin Matuska			return
1258fc25799SMartin Matuska		acct[key] = v
1268fc25799SMartin Matuska
1278fc25799SMartin Matuska	# Add our value to an existing value, which may be present if
1288fc25799SMartin Matuska	# options.translate is set.
1298fc25799SMartin Matuska	value = v[field + ".sort"] = value + v[field + ".sort"]
1308fc25799SMartin Matuska
1318fc25799SMartin Matuska	if options.parsable:
1328fc25799SMartin Matuska		v[field] = str(value)
1338fc25799SMartin Matuska	else:
1348fc25799SMartin Matuska		v[field] = zfs.util.nicenum(value)
1358fc25799SMartin Matuska
1368fc25799SMartin Matuskadef do_userspace():
1378fc25799SMartin Matuska	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
1388fc25799SMartin Matuska
1398fc25799SMartin Matuska	def usage(msg=None):
1408fc25799SMartin Matuska		parser.print_help()
1418fc25799SMartin Matuska		if msg:
1428fc25799SMartin Matuska			print
1438fc25799SMartin Matuska			parser.exit("zfs: error: " + msg)
1448fc25799SMartin Matuska		else:
1458fc25799SMartin Matuska			parser.exit()
1468fc25799SMartin Matuska
1478fc25799SMartin Matuska	if sys.argv[1] == "userspace":
1488fc25799SMartin Matuska		defaulttypes = "posixuser,smbuser"
1498fc25799SMartin Matuska	else:
1508fc25799SMartin Matuska		defaulttypes = "posixgroup,smbgroup"
1518fc25799SMartin Matuska
1528fc25799SMartin Matuska	fields = ("type", "name", "used", "quota")
153*10b9d77bSPawel Jakub Dawidek	rjustfields = ("used", "quota")
1548fc25799SMartin Matuska	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
1558fc25799SMartin Matuska
1568fc25799SMartin Matuska	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
1578fc25799SMartin Matuska	u += _("    [-t type[,...]] <filesystem|snapshot>")
1588fc25799SMartin Matuska	parser = optparse.OptionParser(usage=u, prog="zfs")
1598fc25799SMartin Matuska
1608fc25799SMartin Matuska	parser.add_option("-n", action="store_true", dest="noname",
1618fc25799SMartin Matuska	    help=_("Print numeric ID instead of user/group name"))
1628fc25799SMartin Matuska	parser.add_option("-i", action="store_true", dest="translate",
1638fc25799SMartin Matuska	    help=_("translate SID to posix (possibly ephemeral) ID"))
1648fc25799SMartin Matuska	parser.add_option("-H", action="store_true", dest="noheaders",
1658fc25799SMartin Matuska	    help=_("no headers, tab delimited output"))
1668fc25799SMartin Matuska	parser.add_option("-p", action="store_true", dest="parsable",
1678fc25799SMartin Matuska	    help=_("exact (parsable) numeric output"))
1688fc25799SMartin Matuska	parser.add_option("-o", dest="fields", metavar="field[,...]",
1698fc25799SMartin Matuska	    default="type,name,used,quota",
1708fc25799SMartin Matuska	    help=_("print only these fields (eg type,name,used,quota)"))
1718fc25799SMartin Matuska	parser.add_option("-s", dest="sortfields", metavar="field",
1728fc25799SMartin Matuska	    type="choice", choices=fields, default=list(),
1738fc25799SMartin Matuska	    action="callback", callback=zfs.util.append_with_opt,
1748fc25799SMartin Matuska	    help=_("sort field"))
1758fc25799SMartin Matuska	parser.add_option("-S", dest="sortfields", metavar="field",
1768fc25799SMartin Matuska	    type="choice", choices=fields, #-s sets the default
1778fc25799SMartin Matuska	    action="callback", callback=zfs.util.append_with_opt,
1788fc25799SMartin Matuska	    help=_("reverse sort field"))
1798fc25799SMartin Matuska	parser.add_option("-t", dest="types", metavar="type[,...]",
1808fc25799SMartin Matuska	    default=defaulttypes,
1818fc25799SMartin Matuska	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
1828fc25799SMartin Matuska
1838fc25799SMartin Matuska	(options, args) = parser.parse_args(sys.argv[2:])
1848fc25799SMartin Matuska	if len(args) != 1:
1858fc25799SMartin Matuska		usage(_("wrong number of arguments"))
1868fc25799SMartin Matuska	dsname = args[0]
1878fc25799SMartin Matuska
1888fc25799SMartin Matuska	options.fields = options.fields.split(",")
1898fc25799SMartin Matuska	for f in options.fields:
1908fc25799SMartin Matuska		if f not in fields:
1918fc25799SMartin Matuska			usage(_("invalid field %s") % f)
1928fc25799SMartin Matuska
1938fc25799SMartin Matuska	options.types = options.types.split(",")
1948fc25799SMartin Matuska	for t in options.types:
1958fc25799SMartin Matuska		if t not in types:
1968fc25799SMartin Matuska			usage(_("invalid type %s") % t)
1978fc25799SMartin Matuska
1988fc25799SMartin Matuska	if not options.sortfields:
1998fc25799SMartin Matuska		options.sortfields = [("-s", "type"), ("-s", "name")]
2008fc25799SMartin Matuska
2018fc25799SMartin Matuska	if "all" in options.types:
2028fc25799SMartin Matuska		options.types = types[1:]
2038fc25799SMartin Matuska
2048fc25799SMartin Matuska	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
2058fc25799SMartin Matuska
206*10b9d77bSPawel Jakub Dawidek	if ds.getprop("jailed") and solaris.misc.isglobalzone():
2078fc25799SMartin Matuska		options.noname = True
2088fc25799SMartin Matuska
2098fc25799SMartin Matuska	if not ds.getprop("useraccounting"):
2108fc25799SMartin Matuska		print(_("Initializing accounting information on old filesystem, please wait..."))
2118fc25799SMartin Matuska		ds.userspace_upgrade()
2128fc25799SMartin Matuska
2138fc25799SMartin Matuska	# gather and process accounting information
214*10b9d77bSPawel Jakub Dawidek	# Due to -i, we need to keep a dict, so we can potentially add
215*10b9d77bSPawel Jakub Dawidek	# together the posix ID and SID's usage.  Grr.
216*10b9d77bSPawel Jakub Dawidek	acct = dict()
2178fc25799SMartin Matuska	for prop in props.keys():
2188fc25799SMartin Matuska		if skiptype(options, prop):
2198fc25799SMartin Matuska			continue;
2208fc25799SMartin Matuska		for elem in ds.userspace(prop):
221*10b9d77bSPawel Jakub Dawidek			process_one_raw(acct, options, prop, elem)
2228fc25799SMartin Matuska
2238fc25799SMartin Matuska	def cmpkey(val):
2248fc25799SMartin Matuska		l = list()
2258fc25799SMartin Matuska		for (opt, field) in options.sortfields:
2268fc25799SMartin Matuska			try:
2278fc25799SMartin Matuska				n = val[field + ".sort"]
2288fc25799SMartin Matuska			except KeyError:
2298fc25799SMartin Matuska				n = val[field]
2308fc25799SMartin Matuska			if opt == "-S":
2318fc25799SMartin Matuska				# reverse sorting
2328fc25799SMartin Matuska				try:
2338fc25799SMartin Matuska					n = -n
2348fc25799SMartin Matuska				except TypeError:
2358fc25799SMartin Matuska					# it's a string; decompose it
2368fc25799SMartin Matuska					# into an array of integers,
2378fc25799SMartin Matuska					# each one the negative of that
2388fc25799SMartin Matuska					# character
2398fc25799SMartin Matuska					n = [-ord(c) for c in n]
2408fc25799SMartin Matuska			l.append(n)
2418fc25799SMartin Matuska		return l
2428fc25799SMartin Matuska
243*10b9d77bSPawel Jakub Dawidek	t = zfs.table.Table(options.fields, rjustfields)
244*10b9d77bSPawel Jakub Dawidek	for val in acct.itervalues():
245*10b9d77bSPawel Jakub Dawidek		t.addline(cmpkey(val), val)
246*10b9d77bSPawel Jakub Dawidek	t.printme(not options.noheaders)
247