xref: /illumos-gate/usr/src/lib/pyzfs/common/userspace.py (revision e4d060fb4c00d44cd578713eb9a921f594b733b8)
114843421SMatthew Ahrens#! /usr/bin/python2.4
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#
22*e4d060fbSSam Falkner# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
2314843421SMatthew Ahrens# Use is subject to license terms.
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
34*e4d060fbSSam 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) = {
72*e4d060fbSSam 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),
74*e4d060fbSSam 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:
113*e4d060fbSSam 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)
11914843421SMatthew Ahrens
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
207*e4d060fbSSam 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)
245842727c2SChris Kirby	for val in acct.itervalues():
246842727c2SChris Kirby		t.addline(cmpkey(val), val)
247842727c2SChris Kirby	t.printme(not options.noheaders)
248