19396SMatthew.Ahrens@Sun.COM#! /usr/bin/python2.4 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*11821SSam.Falkner@Sun.COM# Copyright 2010 Sun Microsystems, Inc. All rights reserved. 239396SMatthew.Ahrens@Sun.COM# Use is subject to license terms. 249396SMatthew.Ahrens@Sun.COM# 259396SMatthew.Ahrens@Sun.COM 269396SMatthew.Ahrens@Sun.COM"""This module implements the "zfs userspace" and "zfs groupspace" subcommands. 279396SMatthew.Ahrens@Sun.COMThe only public interface is the zfs.userspace.do_userspace() function.""" 289396SMatthew.Ahrens@Sun.COM 299396SMatthew.Ahrens@Sun.COMimport optparse 309396SMatthew.Ahrens@Sun.COMimport sys 319396SMatthew.Ahrens@Sun.COMimport pwd 329396SMatthew.Ahrens@Sun.COMimport grp 339396SMatthew.Ahrens@Sun.COMimport errno 34*11821SSam.Falkner@Sun.COMimport solaris.misc 3510242Schris.kirby@sun.comimport zfs.util 3610242Schris.kirby@sun.comimport zfs.ioctl 3710242Schris.kirby@sun.comimport zfs.dataset 3810242Schris.kirby@sun.comimport zfs.table 399396SMatthew.Ahrens@Sun.COM 409396SMatthew.Ahrens@Sun.COM_ = zfs.util._ 419396SMatthew.Ahrens@Sun.COM 429396SMatthew.Ahrens@Sun.COM# map from property name prefix -> (field name, isgroup) 439396SMatthew.Ahrens@Sun.COMprops = { 449396SMatthew.Ahrens@Sun.COM "userused@": ("used", False), 459396SMatthew.Ahrens@Sun.COM "userquota@": ("quota", False), 469396SMatthew.Ahrens@Sun.COM "groupused@": ("used", True), 479396SMatthew.Ahrens@Sun.COM "groupquota@": ("quota", True), 489396SMatthew.Ahrens@Sun.COM} 499396SMatthew.Ahrens@Sun.COM 509396SMatthew.Ahrens@Sun.COMdef skiptype(options, prop): 519396SMatthew.Ahrens@Sun.COM """Return True if this property (eg "userquota@") should be skipped.""" 529396SMatthew.Ahrens@Sun.COM (field, isgroup) = props[prop] 539396SMatthew.Ahrens@Sun.COM if field not in options.fields: 549396SMatthew.Ahrens@Sun.COM return True 559396SMatthew.Ahrens@Sun.COM if isgroup and "posixgroup" not in options.types and \ 569396SMatthew.Ahrens@Sun.COM "smbgroup" not in options.types: 579396SMatthew.Ahrens@Sun.COM return True 589396SMatthew.Ahrens@Sun.COM if not isgroup and "posixuser" not in options.types and \ 599396SMatthew.Ahrens@Sun.COM "smbuser" not in options.types: 609396SMatthew.Ahrens@Sun.COM return True 619396SMatthew.Ahrens@Sun.COM return False 629396SMatthew.Ahrens@Sun.COM 639396SMatthew.Ahrens@Sun.COMdef new_entry(options, isgroup, domain, rid): 649396SMatthew.Ahrens@Sun.COM """Return a dict("field": value) for this domain (string) + rid (int)""" 659396SMatthew.Ahrens@Sun.COM 669396SMatthew.Ahrens@Sun.COM if domain: 679396SMatthew.Ahrens@Sun.COM idstr = "%s-%u" % (domain, rid) 689396SMatthew.Ahrens@Sun.COM else: 699396SMatthew.Ahrens@Sun.COM idstr = "%u" % rid 709396SMatthew.Ahrens@Sun.COM 719396SMatthew.Ahrens@Sun.COM (typename, mapfunc) = { 72*11821SSam.Falkner@Sun.COM (1, 1): ("SMB Group", lambda id: solaris.misc.sid_to_name(id, 0)), 739396SMatthew.Ahrens@Sun.COM (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name), 74*11821SSam.Falkner@Sun.COM (0, 1): ("SMB User", lambda id: solaris.misc.sid_to_name(id, 1)), 759396SMatthew.Ahrens@Sun.COM (0, 0): ("POSIX User", lambda id: pwd.getpwuid(int(id)).pw_name) 769396SMatthew.Ahrens@Sun.COM }[isgroup, bool(domain)] 779396SMatthew.Ahrens@Sun.COM 789396SMatthew.Ahrens@Sun.COM if typename.lower().replace(" ", "") not in options.types: 799396SMatthew.Ahrens@Sun.COM return None 809396SMatthew.Ahrens@Sun.COM 819396SMatthew.Ahrens@Sun.COM v = dict() 829396SMatthew.Ahrens@Sun.COM v["type"] = typename 839396SMatthew.Ahrens@Sun.COM 849396SMatthew.Ahrens@Sun.COM # python's getpwuid/getgrgid is confused by ephemeral uids 859396SMatthew.Ahrens@Sun.COM if not options.noname and rid < 1<<31: 869396SMatthew.Ahrens@Sun.COM try: 879396SMatthew.Ahrens@Sun.COM v["name"] = mapfunc(idstr) 889396SMatthew.Ahrens@Sun.COM except KeyError: 899396SMatthew.Ahrens@Sun.COM pass 909396SMatthew.Ahrens@Sun.COM 919396SMatthew.Ahrens@Sun.COM if "name" not in v: 929396SMatthew.Ahrens@Sun.COM v["name"] = idstr 939396SMatthew.Ahrens@Sun.COM if not domain: 949396SMatthew.Ahrens@Sun.COM # it's just a number, so pad it with spaces so 959396SMatthew.Ahrens@Sun.COM # that it will sort numerically 969396SMatthew.Ahrens@Sun.COM v["name.sort"] = "%20d" % rid 979396SMatthew.Ahrens@Sun.COM # fill in default values 989396SMatthew.Ahrens@Sun.COM v["used"] = "0" 999396SMatthew.Ahrens@Sun.COM v["used.sort"] = 0 1009396SMatthew.Ahrens@Sun.COM v["quota"] = "none" 1019396SMatthew.Ahrens@Sun.COM v["quota.sort"] = 0 1029396SMatthew.Ahrens@Sun.COM return v 1039396SMatthew.Ahrens@Sun.COM 10410242Schris.kirby@sun.comdef process_one_raw(acct, options, prop, elem): 10510242Schris.kirby@sun.com """Update the acct dict to incorporate the 1069396SMatthew.Ahrens@Sun.COM information from this elem from Dataset.userspace(prop).""" 1079396SMatthew.Ahrens@Sun.COM 1089396SMatthew.Ahrens@Sun.COM (domain, rid, value) = elem 1099396SMatthew.Ahrens@Sun.COM (field, isgroup) = props[prop] 1109396SMatthew.Ahrens@Sun.COM 1119396SMatthew.Ahrens@Sun.COM if options.translate and domain: 1129396SMatthew.Ahrens@Sun.COM try: 113*11821SSam.Falkner@Sun.COM rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid), 1149396SMatthew.Ahrens@Sun.COM not isgroup) 1159396SMatthew.Ahrens@Sun.COM domain = None 1169396SMatthew.Ahrens@Sun.COM except KeyError: 1179396SMatthew.Ahrens@Sun.COM pass; 1189396SMatthew.Ahrens@Sun.COM key = (isgroup, domain, rid) 1199396SMatthew.Ahrens@Sun.COM 1209396SMatthew.Ahrens@Sun.COM try: 1219396SMatthew.Ahrens@Sun.COM v = acct[key] 1229396SMatthew.Ahrens@Sun.COM except KeyError: 1239396SMatthew.Ahrens@Sun.COM v = new_entry(options, isgroup, domain, rid) 1249396SMatthew.Ahrens@Sun.COM if not v: 1259396SMatthew.Ahrens@Sun.COM return 1269396SMatthew.Ahrens@Sun.COM acct[key] = v 1279396SMatthew.Ahrens@Sun.COM 1289396SMatthew.Ahrens@Sun.COM # Add our value to an existing value, which may be present if 1299396SMatthew.Ahrens@Sun.COM # options.translate is set. 1309396SMatthew.Ahrens@Sun.COM value = v[field + ".sort"] = value + v[field + ".sort"] 1319396SMatthew.Ahrens@Sun.COM 1329396SMatthew.Ahrens@Sun.COM if options.parsable: 1339396SMatthew.Ahrens@Sun.COM v[field] = str(value) 1349396SMatthew.Ahrens@Sun.COM else: 1359396SMatthew.Ahrens@Sun.COM v[field] = zfs.util.nicenum(value) 1369396SMatthew.Ahrens@Sun.COM 1379396SMatthew.Ahrens@Sun.COMdef do_userspace(): 1389396SMatthew.Ahrens@Sun.COM """Implements the "zfs userspace" and "zfs groupspace" subcommands.""" 1399396SMatthew.Ahrens@Sun.COM 1409396SMatthew.Ahrens@Sun.COM def usage(msg=None): 1419396SMatthew.Ahrens@Sun.COM parser.print_help() 1429396SMatthew.Ahrens@Sun.COM if msg: 1439396SMatthew.Ahrens@Sun.COM print 1449396SMatthew.Ahrens@Sun.COM parser.exit("zfs: error: " + msg) 1459396SMatthew.Ahrens@Sun.COM else: 1469396SMatthew.Ahrens@Sun.COM parser.exit() 1479396SMatthew.Ahrens@Sun.COM 1489396SMatthew.Ahrens@Sun.COM if sys.argv[1] == "userspace": 1499396SMatthew.Ahrens@Sun.COM defaulttypes = "posixuser,smbuser" 1509396SMatthew.Ahrens@Sun.COM else: 1519396SMatthew.Ahrens@Sun.COM defaulttypes = "posixgroup,smbgroup" 1529396SMatthew.Ahrens@Sun.COM 1539396SMatthew.Ahrens@Sun.COM fields = ("type", "name", "used", "quota") 15410242Schris.kirby@sun.com rjustfields = ("used", "quota") 1559396SMatthew.Ahrens@Sun.COM types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup") 1569396SMatthew.Ahrens@Sun.COM 1579396SMatthew.Ahrens@Sun.COM u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1] 1589396SMatthew.Ahrens@Sun.COM u += _(" [-t type[,...]] <filesystem|snapshot>") 1599396SMatthew.Ahrens@Sun.COM parser = optparse.OptionParser(usage=u, prog="zfs") 1609396SMatthew.Ahrens@Sun.COM 1619396SMatthew.Ahrens@Sun.COM parser.add_option("-n", action="store_true", dest="noname", 1629396SMatthew.Ahrens@Sun.COM help=_("Print numeric ID instead of user/group name")) 1639396SMatthew.Ahrens@Sun.COM parser.add_option("-i", action="store_true", dest="translate", 1649396SMatthew.Ahrens@Sun.COM help=_("translate SID to posix (possibly ephemeral) ID")) 1659396SMatthew.Ahrens@Sun.COM parser.add_option("-H", action="store_true", dest="noheaders", 1669396SMatthew.Ahrens@Sun.COM help=_("no headers, tab delimited output")) 1679396SMatthew.Ahrens@Sun.COM parser.add_option("-p", action="store_true", dest="parsable", 1689396SMatthew.Ahrens@Sun.COM help=_("exact (parsable) numeric output")) 1699396SMatthew.Ahrens@Sun.COM parser.add_option("-o", dest="fields", metavar="field[,...]", 1709396SMatthew.Ahrens@Sun.COM default="type,name,used,quota", 1719396SMatthew.Ahrens@Sun.COM help=_("print only these fields (eg type,name,used,quota)")) 1729396SMatthew.Ahrens@Sun.COM parser.add_option("-s", dest="sortfields", metavar="field", 1739396SMatthew.Ahrens@Sun.COM type="choice", choices=fields, default=list(), 1749396SMatthew.Ahrens@Sun.COM action="callback", callback=zfs.util.append_with_opt, 1759396SMatthew.Ahrens@Sun.COM help=_("sort field")) 1769396SMatthew.Ahrens@Sun.COM parser.add_option("-S", dest="sortfields", metavar="field", 1779396SMatthew.Ahrens@Sun.COM type="choice", choices=fields, #-s sets the default 1789396SMatthew.Ahrens@Sun.COM action="callback", callback=zfs.util.append_with_opt, 1799396SMatthew.Ahrens@Sun.COM help=_("reverse sort field")) 1809396SMatthew.Ahrens@Sun.COM parser.add_option("-t", dest="types", metavar="type[,...]", 1819396SMatthew.Ahrens@Sun.COM default=defaulttypes, 1829396SMatthew.Ahrens@Sun.COM help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)")) 1839396SMatthew.Ahrens@Sun.COM 1849396SMatthew.Ahrens@Sun.COM (options, args) = parser.parse_args(sys.argv[2:]) 1859396SMatthew.Ahrens@Sun.COM if len(args) != 1: 1869396SMatthew.Ahrens@Sun.COM usage(_("wrong number of arguments")) 1879396SMatthew.Ahrens@Sun.COM dsname = args[0] 1889396SMatthew.Ahrens@Sun.COM 1899396SMatthew.Ahrens@Sun.COM options.fields = options.fields.split(",") 1909396SMatthew.Ahrens@Sun.COM for f in options.fields: 1919396SMatthew.Ahrens@Sun.COM if f not in fields: 1929396SMatthew.Ahrens@Sun.COM usage(_("invalid field %s") % f) 1939396SMatthew.Ahrens@Sun.COM 1949396SMatthew.Ahrens@Sun.COM options.types = options.types.split(",") 1959396SMatthew.Ahrens@Sun.COM for t in options.types: 1969396SMatthew.Ahrens@Sun.COM if t not in types: 1979396SMatthew.Ahrens@Sun.COM usage(_("invalid type %s") % t) 1989396SMatthew.Ahrens@Sun.COM 1999396SMatthew.Ahrens@Sun.COM if not options.sortfields: 2009396SMatthew.Ahrens@Sun.COM options.sortfields = [("-s", "type"), ("-s", "name")] 2019396SMatthew.Ahrens@Sun.COM 2029396SMatthew.Ahrens@Sun.COM if "all" in options.types: 2039396SMatthew.Ahrens@Sun.COM options.types = types[1:] 2049396SMatthew.Ahrens@Sun.COM 2059396SMatthew.Ahrens@Sun.COM ds = zfs.dataset.Dataset(dsname, types=("filesystem")) 2069396SMatthew.Ahrens@Sun.COM 207*11821SSam.Falkner@Sun.COM if ds.getprop("zoned") and solaris.misc.isglobalzone(): 2089396SMatthew.Ahrens@Sun.COM options.noname = True 2099396SMatthew.Ahrens@Sun.COM 2109396SMatthew.Ahrens@Sun.COM if not ds.getprop("useraccounting"): 2119396SMatthew.Ahrens@Sun.COM print(_("Initializing accounting information on old filesystem, please wait...")) 2129396SMatthew.Ahrens@Sun.COM ds.userspace_upgrade() 2139396SMatthew.Ahrens@Sun.COM 21410242Schris.kirby@sun.com # gather and process accounting information 21510242Schris.kirby@sun.com # Due to -i, we need to keep a dict, so we can potentially add 21610242Schris.kirby@sun.com # together the posix ID and SID's usage. Grr. 2179396SMatthew.Ahrens@Sun.COM acct = dict() 2189396SMatthew.Ahrens@Sun.COM for prop in props.keys(): 2199396SMatthew.Ahrens@Sun.COM if skiptype(options, prop): 2209396SMatthew.Ahrens@Sun.COM continue; 2219396SMatthew.Ahrens@Sun.COM for elem in ds.userspace(prop): 22210242Schris.kirby@sun.com process_one_raw(acct, options, prop, elem) 2239396SMatthew.Ahrens@Sun.COM 2249396SMatthew.Ahrens@Sun.COM def cmpkey(val): 2259396SMatthew.Ahrens@Sun.COM l = list() 2269396SMatthew.Ahrens@Sun.COM for (opt, field) in options.sortfields: 2279396SMatthew.Ahrens@Sun.COM try: 2289396SMatthew.Ahrens@Sun.COM n = val[field + ".sort"] 2299396SMatthew.Ahrens@Sun.COM except KeyError: 2309396SMatthew.Ahrens@Sun.COM n = val[field] 2319396SMatthew.Ahrens@Sun.COM if opt == "-S": 2329396SMatthew.Ahrens@Sun.COM # reverse sorting 2339396SMatthew.Ahrens@Sun.COM try: 2349396SMatthew.Ahrens@Sun.COM n = -n 2359396SMatthew.Ahrens@Sun.COM except TypeError: 2369396SMatthew.Ahrens@Sun.COM # it's a string; decompose it 2379396SMatthew.Ahrens@Sun.COM # into an array of integers, 2389396SMatthew.Ahrens@Sun.COM # each one the negative of that 2399396SMatthew.Ahrens@Sun.COM # character 2409396SMatthew.Ahrens@Sun.COM n = [-ord(c) for c in n] 2419396SMatthew.Ahrens@Sun.COM l.append(n) 2429396SMatthew.Ahrens@Sun.COM return l 2439396SMatthew.Ahrens@Sun.COM 24410242Schris.kirby@sun.com t = zfs.table.Table(options.fields, rjustfields) 24510242Schris.kirby@sun.com for val in acct.itervalues(): 24610242Schris.kirby@sun.com t.addline(cmpkey(val), val) 24710242Schris.kirby@sun.com t.printme(not options.noheaders) 248