19f923083SAlexander Pyhalov#!@PYTHON@
214843421SMatthew Ahrens#
314843421SMatthew Ahrens# CDDL HEADER START
414843421SMatthew Ahrens#
514843421SMatthew Ahrens# The contents of this file are subject to the terms of the
614843421SMatthew Ahrens# Common Development and Distribution License (the "License").
714843421SMatthew Ahrens# You may not use this file except in compliance with the License.
814843421SMatthew Ahrens#
914843421SMatthew Ahrens# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1014843421SMatthew Ahrens# or http://www.opensolaris.org/os/licensing.
1114843421SMatthew Ahrens# See the License for the specific language governing permissions
1214843421SMatthew Ahrens# and limitations under the License.
1314843421SMatthew Ahrens#
1414843421SMatthew Ahrens# When distributing Covered Code, include this CDDL HEADER in each
1514843421SMatthew Ahrens# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1614843421SMatthew Ahrens# If applicable, add the following below this CDDL HEADER, with the
1714843421SMatthew Ahrens# fields enclosed by brackets "[]" replaced with your own identifying
1814843421SMatthew Ahrens# information: Portions Copyright [yyyy] [name of copyright owner]
1914843421SMatthew Ahrens#
2014843421SMatthew Ahrens# CDDL HEADER END
2114843421SMatthew Ahrens#
226d52f363SLori Alt# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23*e8921a52SAndy Fiddaman# Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
2414843421SMatthew Ahrens#
2514843421SMatthew Ahrens
2614843421SMatthew Ahrens"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
2714843421SMatthew AhrensThe only public interface is the zfs.userspace.do_userspace() function."""
2814843421SMatthew Ahrens
2914843421SMatthew Ahrensimport optparse
3014843421SMatthew Ahrensimport sys
3114843421SMatthew Ahrensimport pwd
3214843421SMatthew Ahrensimport grp
3314843421SMatthew Ahrensimport errno
34e4d060fbSSam Falknerimport solaris.misc
35842727c2SChris Kirbyimport zfs.util
36842727c2SChris Kirbyimport zfs.ioctl
37842727c2SChris Kirbyimport zfs.dataset
38842727c2SChris Kirbyimport zfs.table
3914843421SMatthew Ahrens
4014843421SMatthew Ahrens_ = zfs.util._
4114843421SMatthew Ahrens
4214843421SMatthew Ahrens# map from property name prefix -> (field name, isgroup)
4314843421SMatthew Ahrensprops = {
4414843421SMatthew Ahrens    "userused@": ("used", False),
4514843421SMatthew Ahrens    "userquota@": ("quota", False),
4614843421SMatthew Ahrens    "groupused@": ("used", True),
4714843421SMatthew Ahrens    "groupquota@": ("quota", True),
4814843421SMatthew Ahrens}
4914843421SMatthew Ahrens
5014843421SMatthew Ahrensdef skiptype(options, prop):
5114843421SMatthew Ahrens	"""Return True if this property (eg "userquota@") should be skipped."""
5214843421SMatthew Ahrens	(field, isgroup) = props[prop]
5314843421SMatthew Ahrens	if field not in options.fields:
5414843421SMatthew Ahrens		return True
5514843421SMatthew Ahrens	if isgroup and "posixgroup" not in options.types and \
5614843421SMatthew Ahrens	    "smbgroup" not in options.types:
5714843421SMatthew Ahrens		return True
5814843421SMatthew Ahrens	if not isgroup and "posixuser" not in options.types and \
5914843421SMatthew Ahrens	    "smbuser" not in options.types:
6014843421SMatthew Ahrens		return True
6114843421SMatthew Ahrens	return False
6214843421SMatthew Ahrens
6314843421SMatthew Ahrensdef new_entry(options, isgroup, domain, rid):
6414843421SMatthew Ahrens	"""Return a dict("field": value) for this domain (string) + rid (int)"""
6514843421SMatthew Ahrens
6614843421SMatthew Ahrens	if domain:
6714843421SMatthew Ahrens		idstr = "%s-%u" % (domain, rid)
6814843421SMatthew Ahrens	else:
6914843421SMatthew Ahrens		idstr = "%u" % rid
7014843421SMatthew Ahrens
7114843421SMatthew Ahrens	(typename, mapfunc) = {
72e4d060fbSSam Falkner	    (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
7314843421SMatthew Ahrens	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
74e4d060fbSSam Falkner	    (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
7514843421SMatthew Ahrens	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
7614843421SMatthew Ahrens	}[isgroup, bool(domain)]
7714843421SMatthew Ahrens
7814843421SMatthew Ahrens	if typename.lower().replace(" ", "") not in options.types:
7914843421SMatthew Ahrens		return None
8014843421SMatthew Ahrens
8114843421SMatthew Ahrens	v = dict()
8214843421SMatthew Ahrens	v["type"] = typename
8314843421SMatthew Ahrens
8414843421SMatthew Ahrens	# python's getpwuid/getgrgid is confused by ephemeral uids
8514843421SMatthew Ahrens	if not options.noname and rid < 1<<31:
8614843421SMatthew Ahrens		try:
8714843421SMatthew Ahrens			v["name"] = mapfunc(idstr)
8814843421SMatthew Ahrens		except KeyError:
8914843421SMatthew Ahrens			pass
9014843421SMatthew Ahrens
9114843421SMatthew Ahrens	if "name" not in v:
9214843421SMatthew Ahrens		v["name"] = idstr
9314843421SMatthew Ahrens		if not domain:
9414843421SMatthew Ahrens			# it's just a number, so pad it with spaces so
9514843421SMatthew Ahrens			# that it will sort numerically
9614843421SMatthew Ahrens			v["name.sort"] = "%20d" % rid
9714843421SMatthew Ahrens	# fill in default values
9814843421SMatthew Ahrens	v["used"] = "0"
9914843421SMatthew Ahrens	v["used.sort"] = 0
10014843421SMatthew Ahrens	v["quota"] = "none"
10114843421SMatthew Ahrens	v["quota.sort"] = 0
10214843421SMatthew Ahrens	return v
10314843421SMatthew Ahrens
104842727c2SChris Kirbydef process_one_raw(acct, options, prop, elem):
105842727c2SChris Kirby	"""Update the acct dict to incorporate the
10614843421SMatthew Ahrens	information from this elem from Dataset.userspace(prop)."""
10714843421SMatthew Ahrens
10814843421SMatthew Ahrens	(domain, rid, value) = elem
10914843421SMatthew Ahrens	(field, isgroup) = props[prop]
11014843421SMatthew Ahrens
11114843421SMatthew Ahrens	if options.translate and domain:
11214843421SMatthew Ahrens		try:
113e4d060fbSSam Falkner			rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
11414843421SMatthew Ahrens			    not isgroup)
11514843421SMatthew Ahrens			domain = None
11614843421SMatthew Ahrens		except KeyError:
11714843421SMatthew Ahrens			pass;
11814843421SMatthew Ahrens	key = (isgroup, domain, rid)
119*e8921a52SAndy Fiddaman
12014843421SMatthew Ahrens	try:
12114843421SMatthew Ahrens		v = acct[key]
12214843421SMatthew Ahrens	except KeyError:
12314843421SMatthew Ahrens		v = new_entry(options, isgroup, domain, rid)
12414843421SMatthew Ahrens		if not v:
12514843421SMatthew Ahrens			return
12614843421SMatthew Ahrens		acct[key] = v
12714843421SMatthew Ahrens
12814843421SMatthew Ahrens	# Add our value to an existing value, which may be present if
12914843421SMatthew Ahrens	# options.translate is set.
13014843421SMatthew Ahrens	value = v[field + ".sort"] = value + v[field + ".sort"]
13114843421SMatthew Ahrens
13214843421SMatthew Ahrens	if options.parsable:
13314843421SMatthew Ahrens		v[field] = str(value)
13414843421SMatthew Ahrens	else:
13514843421SMatthew Ahrens		v[field] = zfs.util.nicenum(value)
13614843421SMatthew Ahrens
13714843421SMatthew Ahrensdef do_userspace():
13814843421SMatthew Ahrens	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
13914843421SMatthew Ahrens
14014843421SMatthew Ahrens	def usage(msg=None):
14114843421SMatthew Ahrens		parser.print_help()
14214843421SMatthew Ahrens		if msg:
14314843421SMatthew Ahrens			print
14414843421SMatthew Ahrens			parser.exit("zfs: error: " + msg)
14514843421SMatthew Ahrens		else:
14614843421SMatthew Ahrens			parser.exit()
14714843421SMatthew Ahrens
14814843421SMatthew Ahrens	if sys.argv[1] == "userspace":
14914843421SMatthew Ahrens		defaulttypes = "posixuser,smbuser"
15014843421SMatthew Ahrens	else:
15114843421SMatthew Ahrens		defaulttypes = "posixgroup,smbgroup"
15214843421SMatthew Ahrens
15314843421SMatthew Ahrens	fields = ("type", "name", "used", "quota")
154842727c2SChris Kirby	rjustfields = ("used", "quota")
15514843421SMatthew Ahrens	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
15614843421SMatthew Ahrens
15714843421SMatthew Ahrens	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
15814843421SMatthew Ahrens	u += _("    [-t type[,...]] <filesystem|snapshot>")
15914843421SMatthew Ahrens	parser = optparse.OptionParser(usage=u, prog="zfs")
16014843421SMatthew Ahrens
16114843421SMatthew Ahrens	parser.add_option("-n", action="store_true", dest="noname",
16214843421SMatthew Ahrens	    help=_("Print numeric ID instead of user/group name"))
16314843421SMatthew Ahrens	parser.add_option("-i", action="store_true", dest="translate",
16414843421SMatthew Ahrens	    help=_("translate SID to posix (possibly ephemeral) ID"))
16514843421SMatthew Ahrens	parser.add_option("-H", action="store_true", dest="noheaders",
16614843421SMatthew Ahrens	    help=_("no headers, tab delimited output"))
16714843421SMatthew Ahrens	parser.add_option("-p", action="store_true", dest="parsable",
16814843421SMatthew Ahrens	    help=_("exact (parsable) numeric output"))
16914843421SMatthew Ahrens	parser.add_option("-o", dest="fields", metavar="field[,...]",
17014843421SMatthew Ahrens	    default="type,name,used,quota",
17114843421SMatthew Ahrens	    help=_("print only these fields (eg type,name,used,quota)"))
17214843421SMatthew Ahrens	parser.add_option("-s", dest="sortfields", metavar="field",
17314843421SMatthew Ahrens	    type="choice", choices=fields, default=list(),
17414843421SMatthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
17514843421SMatthew Ahrens	    help=_("sort field"))
17614843421SMatthew Ahrens	parser.add_option("-S", dest="sortfields", metavar="field",
17714843421SMatthew Ahrens	    type="choice", choices=fields, #-s sets the default
17814843421SMatthew Ahrens	    action="callback", callback=zfs.util.append_with_opt,
17914843421SMatthew Ahrens	    help=_("reverse sort field"))
18014843421SMatthew Ahrens	parser.add_option("-t", dest="types", metavar="type[,...]",
18114843421SMatthew Ahrens	    default=defaulttypes,
18214843421SMatthew Ahrens	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
18314843421SMatthew Ahrens
18414843421SMatthew Ahrens	(options, args) = parser.parse_args(sys.argv[2:])
18514843421SMatthew Ahrens	if len(args) != 1:
18614843421SMatthew Ahrens		usage(_("wrong number of arguments"))
18714843421SMatthew Ahrens	dsname = args[0]
18814843421SMatthew Ahrens
18914843421SMatthew Ahrens	options.fields = options.fields.split(",")
19014843421SMatthew Ahrens	for f in options.fields:
19114843421SMatthew Ahrens		if f not in fields:
19214843421SMatthew Ahrens			usage(_("invalid field %s") % f)
19314843421SMatthew Ahrens
19414843421SMatthew Ahrens	options.types = options.types.split(",")
19514843421SMatthew Ahrens	for t in options.types:
19614843421SMatthew Ahrens		if t not in types:
19714843421SMatthew Ahrens			usage(_("invalid type %s") % t)
19814843421SMatthew Ahrens
19914843421SMatthew Ahrens	if not options.sortfields:
20014843421SMatthew Ahrens		options.sortfields = [("-s", "type"), ("-s", "name")]
20114843421SMatthew Ahrens
20214843421SMatthew Ahrens	if "all" in options.types:
20314843421SMatthew Ahrens		options.types = types[1:]
20414843421SMatthew Ahrens
20514843421SMatthew Ahrens	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
20614843421SMatthew Ahrens
207e4d060fbSSam Falkner	if ds.getprop("zoned") and solaris.misc.isglobalzone():
20814843421SMatthew Ahrens		options.noname = True
20914843421SMatthew Ahrens
21014843421SMatthew Ahrens	if not ds.getprop("useraccounting"):
21114843421SMatthew Ahrens		print(_("Initializing accounting information on old filesystem, please wait..."))
21214843421SMatthew Ahrens		ds.userspace_upgrade()
21314843421SMatthew Ahrens
21414843421SMatthew Ahrens	# gather and process accounting information
215842727c2SChris Kirby	# Due to -i, we need to keep a dict, so we can potentially add
216842727c2SChris Kirby	# together the posix ID and SID's usage.  Grr.
217842727c2SChris Kirby	acct = dict()
21814843421SMatthew Ahrens	for prop in props.keys():
21914843421SMatthew Ahrens		if skiptype(options, prop):
22014843421SMatthew Ahrens			continue;
22114843421SMatthew Ahrens		for elem in ds.userspace(prop):
222842727c2SChris Kirby			process_one_raw(acct, options, prop, elem)
223842727c2SChris Kirby
22414843421SMatthew Ahrens	def cmpkey(val):
22514843421SMatthew Ahrens		l = list()
22614843421SMatthew Ahrens		for (opt, field) in options.sortfields:
22714843421SMatthew Ahrens			try:
22814843421SMatthew Ahrens				n = val[field + ".sort"]
22914843421SMatthew Ahrens			except KeyError:
23014843421SMatthew Ahrens				n = val[field]
23114843421SMatthew Ahrens			if opt == "-S":
23214843421SMatthew Ahrens				# reverse sorting
23314843421SMatthew Ahrens				try:
23414843421SMatthew Ahrens					n = -n
23514843421SMatthew Ahrens				except TypeError:
23614843421SMatthew Ahrens					# it's a string; decompose it
23714843421SMatthew Ahrens					# into an array of integers,
23814843421SMatthew Ahrens					# each one the negative of that
23914843421SMatthew Ahrens					# character
24014843421SMatthew Ahrens					n = [-ord(c) for c in n]
24114843421SMatthew Ahrens			l.append(n)
24214843421SMatthew Ahrens		return l
24314843421SMatthew Ahrens
244842727c2SChris Kirby	t = zfs.table.Table(options.fields, rjustfields)
245*e8921a52SAndy Fiddaman	for val in acct.values():
246842727c2SChris Kirby		t.addline(cmpkey(val), val)
247842727c2SChris Kirby	t.printme(not options.noheaders)
248