19f92308Alexander Pyhalov#!@PYTHON@
21484342Matthew Ahrens#
31484342Matthew Ahrens# CDDL HEADER START
41484342Matthew Ahrens#
51484342Matthew Ahrens# The contents of this file are subject to the terms of the
61484342Matthew Ahrens# Common Development and Distribution License (the "License").
71484342Matthew Ahrens# You may not use this file except in compliance with the License.
81484342Matthew Ahrens#
91484342Matthew Ahrens# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
101484342Matthew Ahrens# or http://www.opensolaris.org/os/licensing.
111484342Matthew Ahrens# See the License for the specific language governing permissions
121484342Matthew Ahrens# and limitations under the License.
131484342Matthew Ahrens#
141484342Matthew Ahrens# When distributing Covered Code, include this CDDL HEADER in each
151484342Matthew Ahrens# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
161484342Matthew Ahrens# If applicable, add the following below this CDDL HEADER, with the
171484342Matthew Ahrens# fields enclosed by brackets "[]" replaced with your own identifying
181484342Matthew Ahrens# information: Portions Copyright [yyyy] [name of copyright owner]
191484342Matthew Ahrens#
201484342Matthew Ahrens# CDDL HEADER END
211484342Matthew Ahrens#
226d52f36Lori Alt# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23e8921a5Andy Fiddaman# Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
241484342Matthew Ahrens#
251484342Matthew Ahrens
261484342Matthew Ahrens"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
271484342Matthew AhrensThe only public interface is the zfs.userspace.do_userspace() function."""
281484342Matthew Ahrens
291484342Matthew Ahrensimport optparse
301484342Matthew Ahrensimport sys
311484342Matthew Ahrensimport pwd
321484342Matthew Ahrensimport grp
331484342Matthew Ahrensimport errno
34e4d060fSam Falknerimport solaris.misc
35842727cChris Kirbyimport zfs.util
36842727cChris Kirbyimport zfs.ioctl
37842727cChris Kirbyimport zfs.dataset
38842727cChris Kirbyimport zfs.table
391484342Matthew Ahrens
401484342Matthew Ahrens_ = zfs.util._
411484342Matthew Ahrens
421484342Matthew Ahrens# map from property name prefix -> (field name, isgroup)
431484342Matthew Ahrensprops = {
441484342Matthew Ahrens    "userused@": ("used", False),
451484342Matthew Ahrens    "userquota@": ("quota", False),
461484342Matthew Ahrens    "groupused@": ("used", True),
471484342Matthew Ahrens    "groupquota@": ("quota", True),
481484342Matthew Ahrens}
491484342Matthew Ahrens
501484342Matthew Ahrensdef skiptype(options, prop):
511484342Matthew Ahrens	"""Return True if this property (eg "userquota@") should be skipped."""
521484342Matthew Ahrens	(field, isgroup) = props[prop]
531484342Matthew Ahrens	if field not in options.fields:
541484342Matthew Ahrens		return True
551484342Matthew Ahrens	if isgroup and "posixgroup" not in options.types and \
561484342Matthew Ahrens	    "smbgroup" not in options.types:
571484342Matthew Ahrens		return True
581484342Matthew Ahrens	if not isgroup and "posixuser" not in options.types and \
591484342Matthew Ahrens	    "smbuser" not in options.types:
601484342Matthew Ahrens		return True
611484342Matthew Ahrens	return False
621484342Matthew Ahrens
631484342Matthew Ahrensdef new_entry(options, isgroup, domain, rid):
641484342Matthew Ahrens	"""Return a dict("field": value) for this domain (string) + rid (int)"""
651484342Matthew Ahrens
661484342Matthew Ahrens	if domain:
671484342Matthew Ahrens		idstr = "%s-%u" % (domain, rid)
681484342Matthew Ahrens	else:
691484342Matthew Ahrens		idstr = "%u" % rid
701484342Matthew Ahrens
711484342Matthew Ahrens	(typename, mapfunc) = {
72e4d060fSam Falkner	    (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
731484342Matthew Ahrens	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
74e4d060fSam Falkner	    (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
751484342Matthew Ahrens	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
761484342Matthew Ahrens	}[isgroup, bool(domain)]
771484342Matthew Ahrens
781484342Matthew Ahrens	if typename.lower().replace(" ", "") not in options.types:
791484342Matthew Ahrens		return None
801484342Matthew Ahrens
811484342Matthew Ahrens	v = dict()
821484342Matthew Ahrens	v["type"] = typename
831484342Matthew Ahrens
841484342Matthew Ahrens	# python's getpwuid/getgrgid is confused by ephemeral uids
851484342Matthew Ahrens	if not options.noname and rid < 1<<31:
861484342Matthew Ahrens		try:
871484342Matthew Ahrens			v["name"] = mapfunc(idstr)
881484342Matthew Ahrens		except KeyError:
891484342Matthew Ahrens			pass
901484342Matthew Ahrens
911484342Matthew Ahrens	if "name" not in v:
921484342Matthew Ahrens		v["name"] = idstr
931484342Matthew Ahrens		if not domain:
941484342Matthew Ahrens			# it's just a number, so pad it with spaces so
951484342Matthew Ahrens			# that it will sort numerically
961484342Matthew Ahrens			v["name.sort"] = "%20d" % rid
971484342Matthew Ahrens	# fill in default values
981484342Matthew Ahrens	v["used"] = "0"
991484342Matthew Ahrens	v["used.sort"] = 0
1001484342Matthew Ahrens	v["quota"] = "none"
1011484342Matthew Ahrens	v["quota.sort"] = 0
1021484342Matthew Ahrens	return v
1031484342Matthew Ahrens
104842727cChris Kirbydef process_one_raw(acct, options, prop, elem):
105842727cChris Kirby	"""Update the acct dict to incorporate the
1061484342Matthew Ahrens	information from this elem from Dataset.userspace(prop)."""
1071484342Matthew Ahrens
1081484342Matthew Ahrens	(domain, rid, value) = elem
1091484342Matthew Ahrens	(field, isgroup) = props[prop]
1101484342Matthew Ahrens
1111484342Matthew Ahrens	if options.translate and domain:
1121484342Matthew Ahrens		try:
113e4d060fSam Falkner			rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
1141484342Matthew Ahrens			    not isgroup)
1151484342Matthew Ahrens			domain = None
1161484342Matthew Ahrens		except KeyError:
1171484342Matthew Ahrens			pass;
1181484342Matthew Ahrens	key = (isgroup, domain, rid)
119e8921a5Andy Fiddaman
1201484342Matthew Ahrens	try:
1211484342Matthew Ahrens		v = acct[key]
1221484342Matthew Ahrens	except KeyError:
1231484342Matthew Ahrens		v = new_entry(options, isgroup, domain, rid)
1241484342Matthew Ahrens		if not v:
1251484342Matthew Ahrens			return
1261484342Matthew Ahrens		acct[key] = v
1271484342Matthew Ahrens
1281484342Matthew Ahrens	# Add our value to an existing value, which may be present if
1291484342Matthew Ahrens	# options.translate is set.
1301484342Matthew Ahrens	value = v[field + ".sort"] = value + v[field + ".sort"]
1311484342Matthew Ahrens
1321484342Matthew Ahrens	if options.parsable:
1331484342Matthew Ahrens		v[field] = str(value)
1341484342Matthew Ahrens	else:
1351484342Matthew Ahrens		v[field] = zfs.util.nicenum(value)
1361484342Matthew Ahrens
1371484342Matthew Ahrensdef do_userspace():
1381484342Matthew Ahrens	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
1391484342Matthew Ahrens
1401484342Matthew Ahrens	def usage(msg=None):
1411484342Matthew Ahrens		parser.print_help()
1421484342Matthew Ahrens		if msg:
1431484342Matthew Ahrens			print
1441484342Matthew Ahrens			parser.exit("zfs: error: " + msg)
1451484342Matthew Ahrens		else:
1461484342Matthew Ahrens			parser.exit()
1471484342Matthew Ahrens
1481484342Matthew Ahrens	if sys.argv[1] == "userspace":
1491484342Matthew Ahrens		defaulttypes = "posixuser,smbuser"
1501484342Matthew Ahrens	else:
1511484342Matthew Ahrens		defaulttypes = "posixgroup,smbgroup"
1521484342Matthew Ahrens
1531484342Matthew Ahrens	fields = ("type", "name", "used", "quota")
154842727cChris Kirby	rjustfields = ("used", "quota")
1551484342Matthew Ahrens	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
1561484342Matthew Ahrens
1571484342Matthew Ahrens	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
1581484342Matthew Ahrens	u += _("    [-t type[,...]] <filesystem|snapshot>")
1591484342Matthew Ahrens	parser = optparse.OptionParser(usage=u, prog="zfs")
1601484342Matthew Ahrens
1611484342Matthew Ahrens	parser.add_option("-n", action="store_true", dest="noname",
1621484342Matthew Ahrens	    help=_("Print numeric ID instead of user/group name"))
1631484342Matthew Ahrens	parser.add_option("-i", action="store_true", dest="translate",
1641484342Matthew Ahrens	    help=_("translate SID to posix (possibly ephemeral) ID"))
1651484342Matthew Ahrens	parser.add_option("-H", action="store_true", dest="noheaders",
1661484342Matthew Ahrens	    help=_("no headers, tab delimited output"))
1671484342Matthew Ahrens	parser.add_option("-p", action="store_true", dest="parsable",
1681484342Matthew Ahrens	    help=_("exact (parsable) numeric output"))
1691484342Matthew Ahrens	parser.add_option("-o", dest="fields", metavar="field[,...]",
1701484342Matthew Ahrens	    default="type,name,used,quota",
1711484342Matthew Ahrens	    help=_("print only these fields (eg type,name,used,quota)"))
1721484342Matthew Ahrens	parser.add_option("-s", dest="sortfields", metavar="field",
1731484342Matthew Ahrens	    type="choice", choices=fields, default=list(),
1741484342Matthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
1751484342Matthew Ahrens	    help=_("sort field"))
1761484342Matthew Ahrens	parser.add_option("-S", dest="sortfields", metavar="field",
1771484342Matthew Ahrens	    type="choice", choices=fields, #-s sets the default
1781484342Matthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
1791484342Matthew Ahrens	    help=_("reverse sort field"))
1801484342Matthew Ahrens	parser.add_option("-t", dest="types", metavar="type[,...]",
1811484342Matthew Ahrens	    default=defaulttypes,
1821484342Matthew Ahrens	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
1831484342Matthew Ahrens
1841484342Matthew Ahrens	(options, args) = parser.parse_args(sys.argv[2:])
1851484342Matthew Ahrens	if len(args) != 1:
1861484342Matthew Ahrens		usage(_("wrong number of arguments"))
1871484342Matthew Ahrens	dsname = args[0]
1881484342Matthew Ahrens
1891484342Matthew Ahrens	options.fields = options.fields.split(",")
1901484342Matthew Ahrens	for f in options.fields:
1911484342Matthew Ahrens		if f not in fields:
1921484342Matthew Ahrens			usage(_("invalid field %s") % f)
1931484342Matthew Ahrens
1941484342Matthew Ahrens	options.types = options.types.split(",")
1951484342Matthew Ahrens	for t in options.types:
1961484342Matthew Ahrens		if t not in types:
1971484342Matthew Ahrens			usage(_("invalid type %s") % t)
1981484342Matthew Ahrens
1991484342Matthew Ahrens	if not options.sortfields:
2001484342Matthew Ahrens		options.sortfields = [("-s", "type"), ("-s", "name")]
2011484342Matthew Ahrens
2021484342Matthew Ahrens	if "all" in options.types:
2031484342Matthew Ahrens		options.types = types[1:]
2041484342Matthew Ahrens
2051484342Matthew Ahrens	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
2061484342Matthew Ahrens
207e4d060fSam Falkner	if ds.getprop("zoned") and solaris.misc.isglobalzone():
2081484342Matthew Ahrens		options.noname = True
2091484342Matthew Ahrens
2101484342Matthew Ahrens	if not ds.getprop("useraccounting"):
2111484342Matthew Ahrens		print(_("Initializing accounting information on old filesystem, please wait..."))
2121484342Matthew Ahrens		ds.userspace_upgrade()
2131484342Matthew Ahrens
2141484342Matthew Ahrens	# gather and process accounting information
215842727cChris Kirby	# Due to -i, we need to keep a dict, so we can potentially add
216842727cChris Kirby	# together the posix ID and SID's usage.  Grr.
217842727cChris Kirby	acct = dict()
2181484342Matthew Ahrens	for prop in props.keys():
2191484342Matthew Ahrens		if skiptype(options, prop):
2201484342Matthew Ahrens			continue;
2211484342Matthew Ahrens		for elem in ds.userspace(prop):
222842727cChris Kirby			process_one_raw(acct, options, prop, elem)
223842727cChris Kirby
2241484342Matthew Ahrens	def cmpkey(val):
2251484342Matthew Ahrens		l = list()
2261484342Matthew Ahrens		for (opt, field) in options.sortfields:
2271484342Matthew Ahrens			try:
2281484342Matthew Ahrens				n = val[field + ".sort"]
2291484342Matthew Ahrens			except KeyError:
2301484342Matthew Ahrens				n = val[field]
2311484342Matthew Ahrens			if opt == "-S":
2321484342Matthew Ahrens				# reverse sorting
2331484342Matthew Ahrens				try:
2341484342Matthew Ahrens					n = -n
2351484342Matthew Ahrens				except TypeError:
2361484342Matthew Ahrens					# it's a string; decompose it
2371484342Matthew Ahrens					# into an array of integers,
2381484342Matthew Ahrens					# each one the negative of that
2391484342Matthew Ahrens					# character
2401484342Matthew Ahrens					n = [-ord(c) for c in n]
2411484342Matthew Ahrens			l.append(n)
2421484342Matthew Ahrens		return l
2431484342Matthew Ahrens
244842727cChris Kirby	t = zfs.table.Table(options.fields, rjustfields)
245e8921a5Andy Fiddaman	for val in acct.values():
246842727cChris Kirby		t.addline(cmpkey(val), val)
247842727cChris Kirby	t.printme(not options.noheaders)
248