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