1#! /usr/bin/python2.6 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 23# 24 25"""This module implements the "zfs userspace" and "zfs groupspace" subcommands. 26The only public interface is the zfs.userspace.do_userspace() function.""" 27 28import optparse 29import sys 30import pwd 31import grp 32import errno 33import solaris.misc 34import zfs.util 35import zfs.ioctl 36import zfs.dataset 37import zfs.table 38 39_ = zfs.util._ 40 41# map from property name prefix -> (field name, isgroup) 42props = { 43 "userused@": ("used", False), 44 "userquota@": ("quota", False), 45 "groupused@": ("used", True), 46 "groupquota@": ("quota", True), 47} 48 49def skiptype(options, prop): 50 """Return True if this property (eg "userquota@") should be skipped.""" 51 (field, isgroup) = props[prop] 52 if field not in options.fields: 53 return True 54 if isgroup and "posixgroup" not in options.types and \ 55 "smbgroup" not in options.types: 56 return True 57 if not isgroup and "posixuser" not in options.types and \ 58 "smbuser" not in options.types: 59 return True 60 return False 61 62def new_entry(options, isgroup, domain, rid): 63 """Return a dict("field": value) for this domain (string) + rid (int)""" 64 65 if domain: 66 idstr = "%s-%u" % (domain, rid) 67 else: 68 idstr = "%u" % rid 69 70 (typename, mapfunc) = { 71 (1, 1): ("SMB Group", lambda id: solaris.misc.sid_to_name(id, 0)), 72 (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name), 73 (0, 1): ("SMB User", lambda id: solaris.misc.sid_to_name(id, 1)), 74 (0, 0): ("POSIX User", lambda id: pwd.getpwuid(int(id)).pw_name) 75 }[isgroup, bool(domain)] 76 77 if typename.lower().replace(" ", "") not in options.types: 78 return None 79 80 v = dict() 81 v["type"] = typename 82 83 # python's getpwuid/getgrgid is confused by ephemeral uids 84 if not options.noname and rid < 1<<31: 85 try: 86 v["name"] = mapfunc(idstr) 87 except KeyError: 88 pass 89 90 if "name" not in v: 91 v["name"] = idstr 92 if not domain: 93 # it's just a number, so pad it with spaces so 94 # that it will sort numerically 95 v["name.sort"] = "%20d" % rid 96 # fill in default values 97 v["used"] = "0" 98 v["used.sort"] = 0 99 v["quota"] = "none" 100 v["quota.sort"] = 0 101 return v 102 103def process_one_raw(acct, options, prop, elem): 104 """Update the acct dict to incorporate the 105 information from this elem from Dataset.userspace(prop).""" 106 107 (domain, rid, value) = elem 108 (field, isgroup) = props[prop] 109 110 if options.translate and domain: 111 try: 112 rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid), 113 not isgroup) 114 domain = None 115 except KeyError: 116 pass; 117 key = (isgroup, domain, rid) 118 119 try: 120 v = acct[key] 121 except KeyError: 122 v = new_entry(options, isgroup, domain, rid) 123 if not v: 124 return 125 acct[key] = v 126 127 # Add our value to an existing value, which may be present if 128 # options.translate is set. 129 value = v[field + ".sort"] = value + v[field + ".sort"] 130 131 if options.parsable: 132 v[field] = str(value) 133 else: 134 v[field] = zfs.util.nicenum(value) 135 136def do_userspace(): 137 """Implements the "zfs userspace" and "zfs groupspace" subcommands.""" 138 139 def usage(msg=None): 140 parser.print_help() 141 if msg: 142 print 143 parser.exit("zfs: error: " + msg) 144 else: 145 parser.exit() 146 147 if sys.argv[1] == "userspace": 148 defaulttypes = "posixuser,smbuser" 149 else: 150 defaulttypes = "posixgroup,smbgroup" 151 152 fields = ("type", "name", "used", "quota") 153 rjustfields = ("used", "quota") 154 types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup") 155 156 u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1] 157 u += _(" [-t type[,...]] <filesystem|snapshot>") 158 parser = optparse.OptionParser(usage=u, prog="zfs") 159 160 parser.add_option("-n", action="store_true", dest="noname", 161 help=_("Print numeric ID instead of user/group name")) 162 parser.add_option("-i", action="store_true", dest="translate", 163 help=_("translate SID to posix (possibly ephemeral) ID")) 164 parser.add_option("-H", action="store_true", dest="noheaders", 165 help=_("no headers, tab delimited output")) 166 parser.add_option("-p", action="store_true", dest="parsable", 167 help=_("exact (parsable) numeric output")) 168 parser.add_option("-o", dest="fields", metavar="field[,...]", 169 default="type,name,used,quota", 170 help=_("print only these fields (eg type,name,used,quota)")) 171 parser.add_option("-s", dest="sortfields", metavar="field", 172 type="choice", choices=fields, default=list(), 173 action="callback", callback=zfs.util.append_with_opt, 174 help=_("sort field")) 175 parser.add_option("-S", dest="sortfields", metavar="field", 176 type="choice", choices=fields, #-s sets the default 177 action="callback", callback=zfs.util.append_with_opt, 178 help=_("reverse sort field")) 179 parser.add_option("-t", dest="types", metavar="type[,...]", 180 default=defaulttypes, 181 help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)")) 182 183 (options, args) = parser.parse_args(sys.argv[2:]) 184 if len(args) != 1: 185 usage(_("wrong number of arguments")) 186 dsname = args[0] 187 188 options.fields = options.fields.split(",") 189 for f in options.fields: 190 if f not in fields: 191 usage(_("invalid field %s") % f) 192 193 options.types = options.types.split(",") 194 for t in options.types: 195 if t not in types: 196 usage(_("invalid type %s") % t) 197 198 if not options.sortfields: 199 options.sortfields = [("-s", "type"), ("-s", "name")] 200 201 if "all" in options.types: 202 options.types = types[1:] 203 204 ds = zfs.dataset.Dataset(dsname, types=("filesystem")) 205 206 if ds.getprop("zoned") and solaris.misc.isglobalzone(): 207 options.noname = True 208 209 if not ds.getprop("useraccounting"): 210 print(_("Initializing accounting information on old filesystem, please wait...")) 211 ds.userspace_upgrade() 212 213 # gather and process accounting information 214 # Due to -i, we need to keep a dict, so we can potentially add 215 # together the posix ID and SID's usage. Grr. 216 acct = dict() 217 for prop in props.keys(): 218 if skiptype(options, prop): 219 continue; 220 for elem in ds.userspace(prop): 221 process_one_raw(acct, options, prop, elem) 222 223 def cmpkey(val): 224 l = list() 225 for (opt, field) in options.sortfields: 226 try: 227 n = val[field + ".sort"] 228 except KeyError: 229 n = val[field] 230 if opt == "-S": 231 # reverse sorting 232 try: 233 n = -n 234 except TypeError: 235 # it's a string; decompose it 236 # into an array of integers, 237 # each one the negative of that 238 # character 239 n = [-ord(c) for c in n] 240 l.append(n) 241 return l 242 243 t = zfs.table.Table(options.fields, rjustfields) 244 for val in acct.itervalues(): 245 t.addline(cmpkey(val), val) 246 t.printme(not options.noheaders) 247