xref: /netbsd-src/external/cddl/osnet/dist/lib/pyzfs/common/userspace.py (revision 3227e6cf668bd374971740bd6660f43cee4417ac)
1*3227e6cfSchs#! /usr/bin/python2.6
2*3227e6cfSchs#
3*3227e6cfSchs# CDDL HEADER START
4*3227e6cfSchs#
5*3227e6cfSchs# The contents of this file are subject to the terms of the
6*3227e6cfSchs# Common Development and Distribution License (the "License").
7*3227e6cfSchs# You may not use this file except in compliance with the License.
8*3227e6cfSchs#
9*3227e6cfSchs# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*3227e6cfSchs# or http://www.opensolaris.org/os/licensing.
11*3227e6cfSchs# See the License for the specific language governing permissions
12*3227e6cfSchs# and limitations under the License.
13*3227e6cfSchs#
14*3227e6cfSchs# When distributing Covered Code, include this CDDL HEADER in each
15*3227e6cfSchs# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*3227e6cfSchs# If applicable, add the following below this CDDL HEADER, with the
17*3227e6cfSchs# fields enclosed by brackets "[]" replaced with your own identifying
18*3227e6cfSchs# information: Portions Copyright [yyyy] [name of copyright owner]
19*3227e6cfSchs#
20*3227e6cfSchs# CDDL HEADER END
21*3227e6cfSchs#
22*3227e6cfSchs# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23*3227e6cfSchs#
24*3227e6cfSchs
25*3227e6cfSchs"""This module implements the "zfs userspace" and "zfs groupspace" subcommands.
26*3227e6cfSchsThe only public interface is the zfs.userspace.do_userspace() function."""
27*3227e6cfSchs
28*3227e6cfSchsimport optparse
29*3227e6cfSchsimport sys
30*3227e6cfSchsimport pwd
31*3227e6cfSchsimport grp
32*3227e6cfSchsimport errno
33*3227e6cfSchsimport solaris.misc
34*3227e6cfSchsimport zfs.util
35*3227e6cfSchsimport zfs.ioctl
36*3227e6cfSchsimport zfs.dataset
37*3227e6cfSchsimport zfs.table
38*3227e6cfSchs
39*3227e6cfSchs_ = zfs.util._
40*3227e6cfSchs
41*3227e6cfSchs# map from property name prefix -> (field name, isgroup)
42*3227e6cfSchsprops = {
43*3227e6cfSchs    "userused@": ("used", False),
44*3227e6cfSchs    "userquota@": ("quota", False),
45*3227e6cfSchs    "groupused@": ("used", True),
46*3227e6cfSchs    "groupquota@": ("quota", True),
47*3227e6cfSchs}
48*3227e6cfSchs
49*3227e6cfSchsdef skiptype(options, prop):
50*3227e6cfSchs	"""Return True if this property (eg "userquota@") should be skipped."""
51*3227e6cfSchs	(field, isgroup) = props[prop]
52*3227e6cfSchs	if field not in options.fields:
53*3227e6cfSchs		return True
54*3227e6cfSchs	if isgroup and "posixgroup" not in options.types and \
55*3227e6cfSchs	    "smbgroup" not in options.types:
56*3227e6cfSchs		return True
57*3227e6cfSchs	if not isgroup and "posixuser" not in options.types and \
58*3227e6cfSchs	    "smbuser" not in options.types:
59*3227e6cfSchs		return True
60*3227e6cfSchs	return False
61*3227e6cfSchs
62*3227e6cfSchsdef new_entry(options, isgroup, domain, rid):
63*3227e6cfSchs	"""Return a dict("field": value) for this domain (string) + rid (int)"""
64*3227e6cfSchs
65*3227e6cfSchs	if domain:
66*3227e6cfSchs		idstr = "%s-%u" % (domain, rid)
67*3227e6cfSchs	else:
68*3227e6cfSchs		idstr = "%u" % rid
69*3227e6cfSchs
70*3227e6cfSchs	(typename, mapfunc) = {
71*3227e6cfSchs	    (1, 1): ("SMB Group",   lambda id: solaris.misc.sid_to_name(id, 0)),
72*3227e6cfSchs	    (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name),
73*3227e6cfSchs	    (0, 1): ("SMB User",    lambda id: solaris.misc.sid_to_name(id, 1)),
74*3227e6cfSchs	    (0, 0): ("POSIX User",  lambda id: pwd.getpwuid(int(id)).pw_name)
75*3227e6cfSchs	}[isgroup, bool(domain)]
76*3227e6cfSchs
77*3227e6cfSchs	if typename.lower().replace(" ", "") not in options.types:
78*3227e6cfSchs		return None
79*3227e6cfSchs
80*3227e6cfSchs	v = dict()
81*3227e6cfSchs	v["type"] = typename
82*3227e6cfSchs
83*3227e6cfSchs	# python's getpwuid/getgrgid is confused by ephemeral uids
84*3227e6cfSchs	if not options.noname and rid < 1<<31:
85*3227e6cfSchs		try:
86*3227e6cfSchs			v["name"] = mapfunc(idstr)
87*3227e6cfSchs		except KeyError:
88*3227e6cfSchs			pass
89*3227e6cfSchs
90*3227e6cfSchs	if "name" not in v:
91*3227e6cfSchs		v["name"] = idstr
92*3227e6cfSchs		if not domain:
93*3227e6cfSchs			# it's just a number, so pad it with spaces so
94*3227e6cfSchs			# that it will sort numerically
95*3227e6cfSchs			v["name.sort"] = "%20d" % rid
96*3227e6cfSchs	# fill in default values
97*3227e6cfSchs	v["used"] = "0"
98*3227e6cfSchs	v["used.sort"] = 0
99*3227e6cfSchs	v["quota"] = "none"
100*3227e6cfSchs	v["quota.sort"] = 0
101*3227e6cfSchs	return v
102*3227e6cfSchs
103*3227e6cfSchsdef process_one_raw(acct, options, prop, elem):
104*3227e6cfSchs	"""Update the acct dict to incorporate the
105*3227e6cfSchs	information from this elem from Dataset.userspace(prop)."""
106*3227e6cfSchs
107*3227e6cfSchs	(domain, rid, value) = elem
108*3227e6cfSchs	(field, isgroup) = props[prop]
109*3227e6cfSchs
110*3227e6cfSchs	if options.translate and domain:
111*3227e6cfSchs		try:
112*3227e6cfSchs			rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid),
113*3227e6cfSchs			    not isgroup)
114*3227e6cfSchs			domain = None
115*3227e6cfSchs		except KeyError:
116*3227e6cfSchs			pass;
117*3227e6cfSchs	key = (isgroup, domain, rid)
118*3227e6cfSchs
119*3227e6cfSchs	try:
120*3227e6cfSchs		v = acct[key]
121*3227e6cfSchs	except KeyError:
122*3227e6cfSchs		v = new_entry(options, isgroup, domain, rid)
123*3227e6cfSchs		if not v:
124*3227e6cfSchs			return
125*3227e6cfSchs		acct[key] = v
126*3227e6cfSchs
127*3227e6cfSchs	# Add our value to an existing value, which may be present if
128*3227e6cfSchs	# options.translate is set.
129*3227e6cfSchs	value = v[field + ".sort"] = value + v[field + ".sort"]
130*3227e6cfSchs
131*3227e6cfSchs	if options.parsable:
132*3227e6cfSchs		v[field] = str(value)
133*3227e6cfSchs	else:
134*3227e6cfSchs		v[field] = zfs.util.nicenum(value)
135*3227e6cfSchs
136*3227e6cfSchsdef do_userspace():
137*3227e6cfSchs	"""Implements the "zfs userspace" and "zfs groupspace" subcommands."""
138*3227e6cfSchs
139*3227e6cfSchs	def usage(msg=None):
140*3227e6cfSchs		parser.print_help()
141*3227e6cfSchs		if msg:
142*3227e6cfSchs			print
143*3227e6cfSchs			parser.exit("zfs: error: " + msg)
144*3227e6cfSchs		else:
145*3227e6cfSchs			parser.exit()
146*3227e6cfSchs
147*3227e6cfSchs	if sys.argv[1] == "userspace":
148*3227e6cfSchs		defaulttypes = "posixuser,smbuser"
149*3227e6cfSchs	else:
150*3227e6cfSchs		defaulttypes = "posixgroup,smbgroup"
151*3227e6cfSchs
152*3227e6cfSchs	fields = ("type", "name", "used", "quota")
153*3227e6cfSchs	rjustfields = ("used", "quota")
154*3227e6cfSchs	types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup")
155*3227e6cfSchs
156*3227e6cfSchs	u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1]
157*3227e6cfSchs	u += _("    [-t type[,...]] <filesystem|snapshot>")
158*3227e6cfSchs	parser = optparse.OptionParser(usage=u, prog="zfs")
159*3227e6cfSchs
160*3227e6cfSchs	parser.add_option("-n", action="store_true", dest="noname",
161*3227e6cfSchs	    help=_("Print numeric ID instead of user/group name"))
162*3227e6cfSchs	parser.add_option("-i", action="store_true", dest="translate",
163*3227e6cfSchs	    help=_("translate SID to posix (possibly ephemeral) ID"))
164*3227e6cfSchs	parser.add_option("-H", action="store_true", dest="noheaders",
165*3227e6cfSchs	    help=_("no headers, tab delimited output"))
166*3227e6cfSchs	parser.add_option("-p", action="store_true", dest="parsable",
167*3227e6cfSchs	    help=_("exact (parsable) numeric output"))
168*3227e6cfSchs	parser.add_option("-o", dest="fields", metavar="field[,...]",
169*3227e6cfSchs	    default="type,name,used,quota",
170*3227e6cfSchs	    help=_("print only these fields (eg type,name,used,quota)"))
171*3227e6cfSchs	parser.add_option("-s", dest="sortfields", metavar="field",
172*3227e6cfSchs	    type="choice", choices=fields, default=list(),
173*3227e6cfSchs	    action="callback", callback=zfs.util.append_with_opt,
174*3227e6cfSchs	    help=_("sort field"))
175*3227e6cfSchs	parser.add_option("-S", dest="sortfields", metavar="field",
176*3227e6cfSchs	    type="choice", choices=fields, #-s sets the default
177*3227e6cfSchs	    action="callback", callback=zfs.util.append_with_opt,
178*3227e6cfSchs	    help=_("reverse sort field"))
179*3227e6cfSchs	parser.add_option("-t", dest="types", metavar="type[,...]",
180*3227e6cfSchs	    default=defaulttypes,
181*3227e6cfSchs	    help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)"))
182*3227e6cfSchs
183*3227e6cfSchs	(options, args) = parser.parse_args(sys.argv[2:])
184*3227e6cfSchs	if len(args) != 1:
185*3227e6cfSchs		usage(_("wrong number of arguments"))
186*3227e6cfSchs	dsname = args[0]
187*3227e6cfSchs
188*3227e6cfSchs	options.fields = options.fields.split(",")
189*3227e6cfSchs	for f in options.fields:
190*3227e6cfSchs		if f not in fields:
191*3227e6cfSchs			usage(_("invalid field %s") % f)
192*3227e6cfSchs
193*3227e6cfSchs	options.types = options.types.split(",")
194*3227e6cfSchs	for t in options.types:
195*3227e6cfSchs		if t not in types:
196*3227e6cfSchs			usage(_("invalid type %s") % t)
197*3227e6cfSchs
198*3227e6cfSchs	if not options.sortfields:
199*3227e6cfSchs		options.sortfields = [("-s", "type"), ("-s", "name")]
200*3227e6cfSchs
201*3227e6cfSchs	if "all" in options.types:
202*3227e6cfSchs		options.types = types[1:]
203*3227e6cfSchs
204*3227e6cfSchs	ds = zfs.dataset.Dataset(dsname, types=("filesystem"))
205*3227e6cfSchs
206*3227e6cfSchs	if ds.getprop("jailed") and solaris.misc.isglobalzone():
207*3227e6cfSchs		options.noname = True
208*3227e6cfSchs
209*3227e6cfSchs	if not ds.getprop("useraccounting"):
210*3227e6cfSchs		print(_("Initializing accounting information on old filesystem, please wait..."))
211*3227e6cfSchs		ds.userspace_upgrade()
212*3227e6cfSchs
213*3227e6cfSchs	# gather and process accounting information
214*3227e6cfSchs	# Due to -i, we need to keep a dict, so we can potentially add
215*3227e6cfSchs	# together the posix ID and SID's usage.  Grr.
216*3227e6cfSchs	acct = dict()
217*3227e6cfSchs	for prop in props.keys():
218*3227e6cfSchs		if skiptype(options, prop):
219*3227e6cfSchs			continue;
220*3227e6cfSchs		for elem in ds.userspace(prop):
221*3227e6cfSchs			process_one_raw(acct, options, prop, elem)
222*3227e6cfSchs
223*3227e6cfSchs	def cmpkey(val):
224*3227e6cfSchs		l = list()
225*3227e6cfSchs		for (opt, field) in options.sortfields:
226*3227e6cfSchs			try:
227*3227e6cfSchs				n = val[field + ".sort"]
228*3227e6cfSchs			except KeyError:
229*3227e6cfSchs				n = val[field]
230*3227e6cfSchs			if opt == "-S":
231*3227e6cfSchs				# reverse sorting
232*3227e6cfSchs				try:
233*3227e6cfSchs					n = -n
234*3227e6cfSchs				except TypeError:
235*3227e6cfSchs					# it's a string; decompose it
236*3227e6cfSchs					# into an array of integers,
237*3227e6cfSchs					# each one the negative of that
238*3227e6cfSchs					# character
239*3227e6cfSchs					n = [-ord(c) for c in n]
240*3227e6cfSchs			l.append(n)
241*3227e6cfSchs		return l
242*3227e6cfSchs
243*3227e6cfSchs	t = zfs.table.Table(options.fields, rjustfields)
244*3227e6cfSchs	for val in acct.itervalues():
245*3227e6cfSchs		t.addline(cmpkey(val), val)
246*3227e6cfSchs	t.printme(not options.noheaders)
247