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