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