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