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