xref: /onnv-gate/usr/src/lib/pyzfs/common/userspace.py (revision 12961:f0448f1d899f)
1*12961Slori.alt@oracle.com#! /usr/bin/python2.6
29396SMatthew.Ahrens@Sun.COM#
39396SMatthew.Ahrens@Sun.COM# CDDL HEADER START
49396SMatthew.Ahrens@Sun.COM#
59396SMatthew.Ahrens@Sun.COM# The contents of this file are subject to the terms of the
69396SMatthew.Ahrens@Sun.COM# Common Development and Distribution License (the "License").
79396SMatthew.Ahrens@Sun.COM# You may not use this file except in compliance with the License.
89396SMatthew.Ahrens@Sun.COM#
99396SMatthew.Ahrens@Sun.COM# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
109396SMatthew.Ahrens@Sun.COM# or http://www.opensolaris.org/os/licensing.
119396SMatthew.Ahrens@Sun.COM# See the License for the specific language governing permissions
129396SMatthew.Ahrens@Sun.COM# and limitations under the License.
139396SMatthew.Ahrens@Sun.COM#
149396SMatthew.Ahrens@Sun.COM# When distributing Covered Code, include this CDDL HEADER in each
159396SMatthew.Ahrens@Sun.COM# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
169396SMatthew.Ahrens@Sun.COM# If applicable, add the following below this CDDL HEADER, with the
179396SMatthew.Ahrens@Sun.COM# fields enclosed by brackets "[]" replaced with your own identifying
189396SMatthew.Ahrens@Sun.COM# information: Portions Copyright [yyyy] [name of copyright owner]
199396SMatthew.Ahrens@Sun.COM#
209396SMatthew.Ahrens@Sun.COM# CDDL HEADER END
219396SMatthew.Ahrens@Sun.COM#
22*12961Slori.alt@oracle.com# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
239396SMatthew.Ahrens@Sun.COM#
249396SMatthew.Ahrens@Sun.COM
259396SMatthew.Ahrens@Sun.COM"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
269396SMatthew.Ahrens@Sun.COMThe only public interface is the zfs.userspace.do_userspace() function."""
279396SMatthew.Ahrens@Sun.COM
289396SMatthew.Ahrens@Sun.COMimport optparse
299396SMatthew.Ahrens@Sun.COMimport sys
309396SMatthew.Ahrens@Sun.COMimport pwd
319396SMatthew.Ahrens@Sun.COMimport grp
329396SMatthew.Ahrens@Sun.COMimport errno
3311821SSam.Falkner@Sun.COMimport solaris.misc
3410242Schris.kirby@sun.comimport zfs.util
3510242Schris.kirby@sun.comimport zfs.ioctl
3610242Schris.kirby@sun.comimport zfs.dataset
3710242Schris.kirby@sun.comimport zfs.table
389396SMatthew.Ahrens@Sun.COM
399396SMatthew.Ahrens@Sun.COM_ = zfs.util._
409396SMatthew.Ahrens@Sun.COM
419396SMatthew.Ahrens@Sun.COM# map from property name prefix -> (field name, isgroup)
429396SMatthew.Ahrens@Sun.COMprops = {
439396SMatthew.Ahrens@Sun.COM    "userused@": ("used", False),
449396SMatthew.Ahrens@Sun.COM    "userquota@": ("quota", False),
459396SMatthew.Ahrens@Sun.COM    "groupused@": ("used", True),
469396SMatthew.Ahrens@Sun.COM    "groupquota@": ("quota", True),
479396SMatthew.Ahrens@Sun.COM}
489396SMatthew.Ahrens@Sun.COM
499396SMatthew.Ahrens@Sun.COMdef skiptype(options, prop):
509396SMatthew.Ahrens@Sun.COM	"""Return True if this property (eg "userquota@") should be skipped."""
519396SMatthew.Ahrens@Sun.COM	(field, isgroup) = props[prop]
529396SMatthew.Ahrens@Sun.COM	if field not in options.fields:
539396SMatthew.Ahrens@Sun.COM		return True
549396SMatthew.Ahrens@Sun.COM	if isgroup and "posixgroup" not in options.types and \
559396SMatthew.Ahrens@Sun.COM	    "smbgroup" not in options.types:
569396SMatthew.Ahrens@Sun.COM		return True
579396SMatthew.Ahrens@Sun.COM	if not isgroup and "posixuser" not in options.types and \
589396SMatthew.Ahrens@Sun.COM	    "smbuser" not in options.types:
599396SMatthew.Ahrens@Sun.COM		return True
609396SMatthew.Ahrens@Sun.COM	return False
619396SMatthew.Ahrens@Sun.COM
629396SMatthew.Ahrens@Sun.COMdef new_entry(options, isgroup, domain, rid):
639396SMatthew.Ahrens@Sun.COM	"""Return a dict("field": value) for this domain (string) + rid (int)"""
649396SMatthew.Ahrens@Sun.COM
659396SMatthew.Ahrens@Sun.COM	if domain:
669396SMatthew.Ahrens@Sun.COM		idstr = "%s-%u" % (domain, rid)
679396SMatthew.Ahrens@Sun.COM	else:
689396SMatthew.Ahrens@Sun.COM		idstr = "%u" % rid
699396SMatthew.Ahrens@Sun.COM
709396SMatthew.Ahrens@Sun.COM	(typename, mapfunc) = {
7111821SSam.Falkner@Sun.COM	    (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
729396SMatthew.Ahrens@Sun.COM	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
7311821SSam.Falkner@Sun.COM	    (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
749396SMatthew.Ahrens@Sun.COM	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
759396SMatthew.Ahrens@Sun.COM	}[isgroup, bool(domain)]
769396SMatthew.Ahrens@Sun.COM
779396SMatthew.Ahrens@Sun.COM	if typename.lower().replace(" ", "") not in options.types:
789396SMatthew.Ahrens@Sun.COM		return None
799396SMatthew.Ahrens@Sun.COM
809396SMatthew.Ahrens@Sun.COM	v = dict()
819396SMatthew.Ahrens@Sun.COM	v["type"] = typename
829396SMatthew.Ahrens@Sun.COM
839396SMatthew.Ahrens@Sun.COM	# python's getpwuid/getgrgid is confused by ephemeral uids
849396SMatthew.Ahrens@Sun.COM	if not options.noname and rid < 1<<31:
859396SMatthew.Ahrens@Sun.COM		try:
869396SMatthew.Ahrens@Sun.COM			v["name"] = mapfunc(idstr)
879396SMatthew.Ahrens@Sun.COM		except KeyError:
889396SMatthew.Ahrens@Sun.COM			pass
899396SMatthew.Ahrens@Sun.COM
909396SMatthew.Ahrens@Sun.COM	if "name" not in v:
919396SMatthew.Ahrens@Sun.COM		v["name"] = idstr
929396SMatthew.Ahrens@Sun.COM		if not domain:
939396SMatthew.Ahrens@Sun.COM			# it's just a number, so pad it with spaces so
949396SMatthew.Ahrens@Sun.COM			# that it will sort numerically
959396SMatthew.Ahrens@Sun.COM			v["name.sort"] = "%20d" % rid
969396SMatthew.Ahrens@Sun.COM	# fill in default values
979396SMatthew.Ahrens@Sun.COM	v["used"] = "0"
989396SMatthew.Ahrens@Sun.COM	v["used.sort"] = 0
999396SMatthew.Ahrens@Sun.COM	v["quota"] = "none"
1009396SMatthew.Ahrens@Sun.COM	v["quota.sort"] = 0
1019396SMatthew.Ahrens@Sun.COM	return v
1029396SMatthew.Ahrens@Sun.COM
10310242Schris.kirby@sun.comdef process_one_raw(acct, options, prop, elem):
10410242Schris.kirby@sun.com	"""Update the acct dict to incorporate the
1059396SMatthew.Ahrens@Sun.COM	information from this elem from Dataset.userspace(prop)."""
1069396SMatthew.Ahrens@Sun.COM
1079396SMatthew.Ahrens@Sun.COM	(domain, rid, value) = elem
1089396SMatthew.Ahrens@Sun.COM	(field, isgroup) = props[prop]
1099396SMatthew.Ahrens@Sun.COM
1109396SMatthew.Ahrens@Sun.COM	if options.translate and domain:
1119396SMatthew.Ahrens@Sun.COM		try:
11211821SSam.Falkner@Sun.COM			rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
1139396SMatthew.Ahrens@Sun.COM			    not isgroup)
1149396SMatthew.Ahrens@Sun.COM			domain = None
1159396SMatthew.Ahrens@Sun.COM		except KeyError:
1169396SMatthew.Ahrens@Sun.COM			pass;
1179396SMatthew.Ahrens@Sun.COM	key = (isgroup, domain, rid)
1189396SMatthew.Ahrens@Sun.COM
1199396SMatthew.Ahrens@Sun.COM	try:
1209396SMatthew.Ahrens@Sun.COM		v = acct[key]
1219396SMatthew.Ahrens@Sun.COM	except KeyError:
1229396SMatthew.Ahrens@Sun.COM		v = new_entry(options, isgroup, domain, rid)
1239396SMatthew.Ahrens@Sun.COM		if not v:
1249396SMatthew.Ahrens@Sun.COM			return
1259396SMatthew.Ahrens@Sun.COM		acct[key] = v
1269396SMatthew.Ahrens@Sun.COM
1279396SMatthew.Ahrens@Sun.COM	# Add our value to an existing value, which may be present if
1289396SMatthew.Ahrens@Sun.COM	# options.translate is set.
1299396SMatthew.Ahrens@Sun.COM	value = v[field + ".sort"] = value + v[field + ".sort"]
1309396SMatthew.Ahrens@Sun.COM
1319396SMatthew.Ahrens@Sun.COM	if options.parsable:
1329396SMatthew.Ahrens@Sun.COM		v[field] = str(value)
1339396SMatthew.Ahrens@Sun.COM	else:
1349396SMatthew.Ahrens@Sun.COM		v[field] = zfs.util.nicenum(value)
1359396SMatthew.Ahrens@Sun.COM
1369396SMatthew.Ahrens@Sun.COMdef do_userspace():
1379396SMatthew.Ahrens@Sun.COM	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
1389396SMatthew.Ahrens@Sun.COM
1399396SMatthew.Ahrens@Sun.COM	def usage(msg=None):
1409396SMatthew.Ahrens@Sun.COM		parser.print_help()
1419396SMatthew.Ahrens@Sun.COM		if msg:
1429396SMatthew.Ahrens@Sun.COM			print
1439396SMatthew.Ahrens@Sun.COM			parser.exit("zfs: error: " + msg)
1449396SMatthew.Ahrens@Sun.COM		else:
1459396SMatthew.Ahrens@Sun.COM			parser.exit()
1469396SMatthew.Ahrens@Sun.COM
1479396SMatthew.Ahrens@Sun.COM	if sys.argv[1] == "userspace":
1489396SMatthew.Ahrens@Sun.COM		defaulttypes = "posixuser,smbuser"
1499396SMatthew.Ahrens@Sun.COM	else:
1509396SMatthew.Ahrens@Sun.COM		defaulttypes = "posixgroup,smbgroup"
1519396SMatthew.Ahrens@Sun.COM
1529396SMatthew.Ahrens@Sun.COM	fields = ("type", "name", "used", "quota")
15310242Schris.kirby@sun.com	rjustfields = ("used", "quota")
1549396SMatthew.Ahrens@Sun.COM	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
1559396SMatthew.Ahrens@Sun.COM
1569396SMatthew.Ahrens@Sun.COM	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
1579396SMatthew.Ahrens@Sun.COM	u += _("    [-t type[,...]] <filesystem|snapshot>")
1589396SMatthew.Ahrens@Sun.COM	parser = optparse.OptionParser(usage=u, prog="zfs")
1599396SMatthew.Ahrens@Sun.COM
1609396SMatthew.Ahrens@Sun.COM	parser.add_option("-n", action="store_true", dest="noname",
1619396SMatthew.Ahrens@Sun.COM	    help=_("Print numeric ID instead of user/group name"))
1629396SMatthew.Ahrens@Sun.COM	parser.add_option("-i", action="store_true", dest="translate",
1639396SMatthew.Ahrens@Sun.COM	    help=_("translate SID to posix (possibly ephemeral) ID"))
1649396SMatthew.Ahrens@Sun.COM	parser.add_option("-H", action="store_true", dest="noheaders",
1659396SMatthew.Ahrens@Sun.COM	    help=_("no headers, tab delimited output"))
1669396SMatthew.Ahrens@Sun.COM	parser.add_option("-p", action="store_true", dest="parsable",
1679396SMatthew.Ahrens@Sun.COM	    help=_("exact (parsable) numeric output"))
1689396SMatthew.Ahrens@Sun.COM	parser.add_option("-o", dest="fields", metavar="field[,...]",
1699396SMatthew.Ahrens@Sun.COM	    default="type,name,used,quota",
1709396SMatthew.Ahrens@Sun.COM	    help=_("print only these fields (eg type,name,used,quota)"))
1719396SMatthew.Ahrens@Sun.COM	parser.add_option("-s", dest="sortfields", metavar="field",
1729396SMatthew.Ahrens@Sun.COM	    type="choice", choices=fields, default=list(),
1739396SMatthew.Ahrens@Sun.COM	    action="callback", callback=zfs.util.append_with_opt,
1749396SMatthew.Ahrens@Sun.COM	    help=_("sort field"))
1759396SMatthew.Ahrens@Sun.COM	parser.add_option("-S", dest="sortfields", metavar="field",
1769396SMatthew.Ahrens@Sun.COM	    type="choice", choices=fields, #-s sets the default
1779396SMatthew.Ahrens@Sun.COM	    action="callback", callback=zfs.util.append_with_opt,
1789396SMatthew.Ahrens@Sun.COM	    help=_("reverse sort field"))
1799396SMatthew.Ahrens@Sun.COM	parser.add_option("-t", dest="types", metavar="type[,...]",
1809396SMatthew.Ahrens@Sun.COM	    default=defaulttypes,
1819396SMatthew.Ahrens@Sun.COM	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
1829396SMatthew.Ahrens@Sun.COM
1839396SMatthew.Ahrens@Sun.COM	(options, args) = parser.parse_args(sys.argv[2:])
1849396SMatthew.Ahrens@Sun.COM	if len(args) != 1:
1859396SMatthew.Ahrens@Sun.COM		usage(_("wrong number of arguments"))
1869396SMatthew.Ahrens@Sun.COM	dsname = args[0]
1879396SMatthew.Ahrens@Sun.COM
1889396SMatthew.Ahrens@Sun.COM	options.fields = options.fields.split(",")
1899396SMatthew.Ahrens@Sun.COM	for f in options.fields:
1909396SMatthew.Ahrens@Sun.COM		if f not in fields:
1919396SMatthew.Ahrens@Sun.COM			usage(_("invalid field %s") % f)
1929396SMatthew.Ahrens@Sun.COM
1939396SMatthew.Ahrens@Sun.COM	options.types = options.types.split(",")
1949396SMatthew.Ahrens@Sun.COM	for t in options.types:
1959396SMatthew.Ahrens@Sun.COM		if t not in types:
1969396SMatthew.Ahrens@Sun.COM			usage(_("invalid type %s") % t)
1979396SMatthew.Ahrens@Sun.COM
1989396SMatthew.Ahrens@Sun.COM	if not options.sortfields:
1999396SMatthew.Ahrens@Sun.COM		options.sortfields = [("-s", "type"), ("-s", "name")]
2009396SMatthew.Ahrens@Sun.COM
2019396SMatthew.Ahrens@Sun.COM	if "all" in options.types:
2029396SMatthew.Ahrens@Sun.COM		options.types = types[1:]
2039396SMatthew.Ahrens@Sun.COM
2049396SMatthew.Ahrens@Sun.COM	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
2059396SMatthew.Ahrens@Sun.COM
20611821SSam.Falkner@Sun.COM	if ds.getprop("zoned") and solaris.misc.isglobalzone():
2079396SMatthew.Ahrens@Sun.COM		options.noname = True
2089396SMatthew.Ahrens@Sun.COM
2099396SMatthew.Ahrens@Sun.COM	if not ds.getprop("useraccounting"):
2109396SMatthew.Ahrens@Sun.COM		print(_("Initializing accounting information on old filesystem, please wait..."))
2119396SMatthew.Ahrens@Sun.COM		ds.userspace_upgrade()
2129396SMatthew.Ahrens@Sun.COM
21310242Schris.kirby@sun.com	# gather and process accounting information
21410242Schris.kirby@sun.com	# Due to -i, we need to keep a dict, so we can potentially add
21510242Schris.kirby@sun.com	# together the posix ID and SID's usage.  Grr.
2169396SMatthew.Ahrens@Sun.COM	acct = dict()
2179396SMatthew.Ahrens@Sun.COM	for prop in props.keys():
2189396SMatthew.Ahrens@Sun.COM		if skiptype(options, prop):
2199396SMatthew.Ahrens@Sun.COM			continue;
2209396SMatthew.Ahrens@Sun.COM		for elem in ds.userspace(prop):
22110242Schris.kirby@sun.com			process_one_raw(acct, options, prop, elem)
2229396SMatthew.Ahrens@Sun.COM
2239396SMatthew.Ahrens@Sun.COM	def cmpkey(val):
2249396SMatthew.Ahrens@Sun.COM		l = list()
2259396SMatthew.Ahrens@Sun.COM		for (opt, field) in options.sortfields:
2269396SMatthew.Ahrens@Sun.COM			try:
2279396SMatthew.Ahrens@Sun.COM				n = val[field + ".sort"]
2289396SMatthew.Ahrens@Sun.COM			except KeyError:
2299396SMatthew.Ahrens@Sun.COM				n = val[field]
2309396SMatthew.Ahrens@Sun.COM			if opt == "-S":
2319396SMatthew.Ahrens@Sun.COM				# reverse sorting
2329396SMatthew.Ahrens@Sun.COM				try:
2339396SMatthew.Ahrens@Sun.COM					n = -n
2349396SMatthew.Ahrens@Sun.COM				except TypeError:
2359396SMatthew.Ahrens@Sun.COM					# it's a string; decompose it
2369396SMatthew.Ahrens@Sun.COM					# into an array of integers,
2379396SMatthew.Ahrens@Sun.COM					# each one the negative of that
2389396SMatthew.Ahrens@Sun.COM					# character
2399396SMatthew.Ahrens@Sun.COM					n = [-ord(c) for c in n]
2409396SMatthew.Ahrens@Sun.COM			l.append(n)
2419396SMatthew.Ahrens@Sun.COM		return l
2429396SMatthew.Ahrens@Sun.COM
24310242Schris.kirby@sun.com	t = zfs.table.Table(options.fields, rjustfields)
24410242Schris.kirby@sun.com	for val in acct.itervalues():
24510242Schris.kirby@sun.com		t.addline(cmpkey(val), val)
24610242Schris.kirby@sun.com	t.printme(not options.noheaders)
247