1219089Spjd#! /usr/bin/python2.6 2209962Smm# 3209962Smm# CDDL HEADER START 4209962Smm# 5209962Smm# The contents of this file are subject to the terms of the 6209962Smm# Common Development and Distribution License (the "License"). 7209962Smm# You may not use this file except in compliance with the License. 8209962Smm# 9209962Smm# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10209962Smm# or http://www.opensolaris.org/os/licensing. 11209962Smm# See the License for the specific language governing permissions 12209962Smm# and limitations under the License. 13209962Smm# 14209962Smm# When distributing Covered Code, include this CDDL HEADER in each 15209962Smm# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16209962Smm# If applicable, add the following below this CDDL HEADER, with the 17209962Smm# fields enclosed by brackets "[]" replaced with your own identifying 18209962Smm# information: Portions Copyright [yyyy] [name of copyright owner] 19209962Smm# 20209962Smm# CDDL HEADER END 21209962Smm# 22219089Spjd# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 23209962Smm# 24209962Smm 25209962Smm"""This module implements the "zfs userspace" and "zfs groupspace" subcommands. 26209962SmmThe only public interface is the zfs.userspace.do_userspace() function.""" 27209962Smm 28209962Smmimport optparse 29209962Smmimport sys 30209962Smmimport pwd 31209962Smmimport grp 32209962Smmimport errno 33219089Spjdimport solaris.misc 34219089Spjdimport zfs.util 35219089Spjdimport zfs.ioctl 36219089Spjdimport zfs.dataset 37219089Spjdimport zfs.table 38209962Smm 39209962Smm_ = zfs.util._ 40209962Smm 41209962Smm# map from property name prefix -> (field name, isgroup) 42209962Smmprops = { 43209962Smm "userused@": ("used", False), 44209962Smm "userquota@": ("quota", False), 45209962Smm "groupused@": ("used", True), 46209962Smm "groupquota@": ("quota", True), 47209962Smm} 48209962Smm 49209962Smmdef skiptype(options, prop): 50209962Smm """Return True if this property (eg "userquota@") should be skipped.""" 51209962Smm (field, isgroup) = props[prop] 52209962Smm if field not in options.fields: 53209962Smm return True 54209962Smm if isgroup and "posixgroup" not in options.types and \ 55209962Smm "smbgroup" not in options.types: 56209962Smm return True 57209962Smm if not isgroup and "posixuser" not in options.types and \ 58209962Smm "smbuser" not in options.types: 59209962Smm return True 60209962Smm return False 61209962Smm 62209962Smmdef new_entry(options, isgroup, domain, rid): 63209962Smm """Return a dict("field": value) for this domain (string) + rid (int)""" 64209962Smm 65209962Smm if domain: 66209962Smm idstr = "%s-%u" % (domain, rid) 67209962Smm else: 68209962Smm idstr = "%u" % rid 69209962Smm 70209962Smm (typename, mapfunc) = { 71219089Spjd (1, 1): ("SMB Group", lambda id: solaris.misc.sid_to_name(id, 0)), 72209962Smm (1, 0): ("POSIX Group", lambda id: grp.getgrgid(int(id)).gr_name), 73219089Spjd (0, 1): ("SMB User", lambda id: solaris.misc.sid_to_name(id, 1)), 74209962Smm (0, 0): ("POSIX User", lambda id: pwd.getpwuid(int(id)).pw_name) 75209962Smm }[isgroup, bool(domain)] 76209962Smm 77209962Smm if typename.lower().replace(" ", "") not in options.types: 78209962Smm return None 79209962Smm 80209962Smm v = dict() 81209962Smm v["type"] = typename 82209962Smm 83209962Smm # python's getpwuid/getgrgid is confused by ephemeral uids 84209962Smm if not options.noname and rid < 1<<31: 85209962Smm try: 86209962Smm v["name"] = mapfunc(idstr) 87209962Smm except KeyError: 88209962Smm pass 89209962Smm 90209962Smm if "name" not in v: 91209962Smm v["name"] = idstr 92209962Smm if not domain: 93209962Smm # it's just a number, so pad it with spaces so 94209962Smm # that it will sort numerically 95209962Smm v["name.sort"] = "%20d" % rid 96209962Smm # fill in default values 97209962Smm v["used"] = "0" 98209962Smm v["used.sort"] = 0 99209962Smm v["quota"] = "none" 100209962Smm v["quota.sort"] = 0 101209962Smm return v 102209962Smm 103219089Spjddef process_one_raw(acct, options, prop, elem): 104219089Spjd """Update the acct dict to incorporate the 105209962Smm information from this elem from Dataset.userspace(prop).""" 106209962Smm 107209962Smm (domain, rid, value) = elem 108209962Smm (field, isgroup) = props[prop] 109209962Smm 110209962Smm if options.translate and domain: 111209962Smm try: 112219089Spjd rid = solaris.misc.sid_to_id("%s-%u" % (domain, rid), 113209962Smm not isgroup) 114209962Smm domain = None 115209962Smm except KeyError: 116209962Smm pass; 117209962Smm key = (isgroup, domain, rid) 118209962Smm 119209962Smm try: 120209962Smm v = acct[key] 121209962Smm except KeyError: 122209962Smm v = new_entry(options, isgroup, domain, rid) 123209962Smm if not v: 124209962Smm return 125209962Smm acct[key] = v 126209962Smm 127209962Smm # Add our value to an existing value, which may be present if 128209962Smm # options.translate is set. 129209962Smm value = v[field + ".sort"] = value + v[field + ".sort"] 130209962Smm 131209962Smm if options.parsable: 132209962Smm v[field] = str(value) 133209962Smm else: 134209962Smm v[field] = zfs.util.nicenum(value) 135209962Smm 136209962Smmdef do_userspace(): 137209962Smm """Implements the "zfs userspace" and "zfs groupspace" subcommands.""" 138209962Smm 139209962Smm def usage(msg=None): 140209962Smm parser.print_help() 141209962Smm if msg: 142209962Smm print 143209962Smm parser.exit("zfs: error: " + msg) 144209962Smm else: 145209962Smm parser.exit() 146209962Smm 147209962Smm if sys.argv[1] == "userspace": 148209962Smm defaulttypes = "posixuser,smbuser" 149209962Smm else: 150209962Smm defaulttypes = "posixgroup,smbgroup" 151209962Smm 152209962Smm fields = ("type", "name", "used", "quota") 153219089Spjd rjustfields = ("used", "quota") 154209962Smm types = ("all", "posixuser", "smbuser", "posixgroup", "smbgroup") 155209962Smm 156209962Smm u = _("%s [-niHp] [-o field[,...]] [-sS field] ... \n") % sys.argv[1] 157209962Smm u += _(" [-t type[,...]] <filesystem|snapshot>") 158209962Smm parser = optparse.OptionParser(usage=u, prog="zfs") 159209962Smm 160209962Smm parser.add_option("-n", action="store_true", dest="noname", 161209962Smm help=_("Print numeric ID instead of user/group name")) 162209962Smm parser.add_option("-i", action="store_true", dest="translate", 163209962Smm help=_("translate SID to posix (possibly ephemeral) ID")) 164209962Smm parser.add_option("-H", action="store_true", dest="noheaders", 165209962Smm help=_("no headers, tab delimited output")) 166209962Smm parser.add_option("-p", action="store_true", dest="parsable", 167209962Smm help=_("exact (parsable) numeric output")) 168209962Smm parser.add_option("-o", dest="fields", metavar="field[,...]", 169209962Smm default="type,name,used,quota", 170209962Smm help=_("print only these fields (eg type,name,used,quota)")) 171209962Smm parser.add_option("-s", dest="sortfields", metavar="field", 172209962Smm type="choice", choices=fields, default=list(), 173209962Smm action="callback", callback=zfs.util.append_with_opt, 174209962Smm help=_("sort field")) 175209962Smm parser.add_option("-S", dest="sortfields", metavar="field", 176209962Smm type="choice", choices=fields, #-s sets the default 177209962Smm action="callback", callback=zfs.util.append_with_opt, 178209962Smm help=_("reverse sort field")) 179209962Smm parser.add_option("-t", dest="types", metavar="type[,...]", 180209962Smm default=defaulttypes, 181209962Smm help=_("print only these types (eg posixuser,smbuser,posixgroup,smbgroup,all)")) 182209962Smm 183209962Smm (options, args) = parser.parse_args(sys.argv[2:]) 184209962Smm if len(args) != 1: 185209962Smm usage(_("wrong number of arguments")) 186209962Smm dsname = args[0] 187209962Smm 188209962Smm options.fields = options.fields.split(",") 189209962Smm for f in options.fields: 190209962Smm if f not in fields: 191209962Smm usage(_("invalid field %s") % f) 192209962Smm 193209962Smm options.types = options.types.split(",") 194209962Smm for t in options.types: 195209962Smm if t not in types: 196209962Smm usage(_("invalid type %s") % t) 197209962Smm 198209962Smm if not options.sortfields: 199209962Smm options.sortfields = [("-s", "type"), ("-s", "name")] 200209962Smm 201209962Smm if "all" in options.types: 202209962Smm options.types = types[1:] 203209962Smm 204209962Smm ds = zfs.dataset.Dataset(dsname, types=("filesystem")) 205209962Smm 206219089Spjd if ds.getprop("jailed") and solaris.misc.isglobalzone(): 207209962Smm options.noname = True 208209962Smm 209209962Smm if not ds.getprop("useraccounting"): 210209962Smm print(_("Initializing accounting information on old filesystem, please wait...")) 211209962Smm ds.userspace_upgrade() 212209962Smm 213219089Spjd # gather and process accounting information 214219089Spjd # Due to -i, we need to keep a dict, so we can potentially add 215219089Spjd # together the posix ID and SID's usage. Grr. 216209962Smm acct = dict() 217209962Smm for prop in props.keys(): 218209962Smm if skiptype(options, prop): 219209962Smm continue; 220209962Smm for elem in ds.userspace(prop): 221219089Spjd process_one_raw(acct, options, prop, elem) 222209962Smm 223209962Smm def cmpkey(val): 224209962Smm l = list() 225209962Smm for (opt, field) in options.sortfields: 226209962Smm try: 227209962Smm n = val[field + ".sort"] 228209962Smm except KeyError: 229209962Smm n = val[field] 230209962Smm if opt == "-S": 231209962Smm # reverse sorting 232209962Smm try: 233209962Smm n = -n 234209962Smm except TypeError: 235209962Smm # it's a string; decompose it 236209962Smm # into an array of integers, 237209962Smm # each one the negative of that 238209962Smm # character 239209962Smm n = [-ord(c) for c in n] 240209962Smm l.append(n) 241209962Smm return l 242209962Smm 243219089Spjd t = zfs.table.Table(options.fields, rjustfields) 244219089Spjd for val in acct.itervalues(): 245219089Spjd t.addline(cmpkey(val), val) 246219089Spjd t.printme(not options.noheaders) 247