]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph: add -v for version. Makefile processes ceph_ver.h
authorDan Mick <dan.mick@inktank.com>
Fri, 7 Jun 2013 03:58:51 +0000 (20:58 -0700)
committerDan Mick <dan.mick@inktank.com>
Sat, 8 Jun 2013 00:20:27 +0000 (17:20 -0700)
Signed-off-by: Dan Mick <dan.mick@inktank.com>
src/Makefile.am
src/ceph [deleted file]
src/ceph.in [new file with mode: 0755]

index e851f9888bf1a409c4fba484bc3226e0f1e6655e..c792f1042233b0c27fbc28bead74cf4eb5e6bf0a 100644 (file)
@@ -1240,7 +1240,7 @@ EXTRA_DIST += \
        $(srcdir)/upstart/radosgw.conf \
        $(srcdir)/upstart/radosgw-all.conf \
        $(srcdir)/upstart/radosgw-all-starter.conf \
-       ceph \
+       ceph.in \
        ceph-disk \
        ceph-disk-prepare \
        ceph-disk-activate \
@@ -1305,6 +1305,21 @@ ceph_ver.c: ./ceph_ver.h
 common/version.cc: ./ceph_ver.h
 test/encoding/ceph_dencoder.cc: ./ceph_ver.h
 
+# assemble Python script with global version variables
+# NB: depends on format of ceph_ver.h
+
+ceph: ceph.in ./ceph_ver.h Makefile
+       rm -f $@ $@.tmp
+       echo "#!/usr/bin/python" >$@.tmp
+       grep "#define CEPH_GIT_NICE_VER" ./ceph_ver.h | \
+               sed -e 's/#define \(.*VER\) /\1=/' >>$@.tmp
+       grep "#define CEPH_GIT_VER" ./ceph_ver.h | \
+         sed -e 's/#define \(.*VER\) /\1=/' -e 's/=\(.*\)$$/="\1"/' >>$@.tmp
+       cat $@.in >>$@.tmp
+       chmod a+x $@.tmp
+       chmod a-w $@.tmp
+       mv $@.tmp $@
+
 # cleaning
 clean-local:
        -rm *.so *.gcno *.gcda
diff --git a/src/ceph b/src/ceph
deleted file mode 100755 (executable)
index 678d834..0000000
--- a/src/ceph
+++ /dev/null
@@ -1,1501 +0,0 @@
-#!/usr/bin/python
-
-import argparse
-import copy
-import errno
-import json
-import os
-import rados
-# for raw_input to do readline cmd editing
-import readline
-import socket
-import stat
-import string
-import struct
-import subprocess
-import sys
-import types
-import uuid
-
-# just a couple of globals
-
-verbose = False
-cluster_handle = None
-
-class ArgumentError(Exception):
-    """
-    Something wrong with arguments
-    """
-    pass
-
-class ArgumentNumber(ArgumentError):
-    """
-    Wrong number of a repeated argument
-    """
-    pass
-
-class ArgumentFormat(ArgumentError):
-    """
-    Argument value has wrong format
-    """
-    pass
-
-class ArgumentValid(ArgumentError):
-    """
-    Argument value is otherwise invalid (doesn't match choices, for instance)
-    """
-    pass
-
-class ArgumentPrefix(ArgumentError):
-    """
-    Special for mismatched prefix; less severe, don't report by default
-    """
-    pass
-
-class JsonFormat(Exception):
-    """
-    some syntactic or semantic issue with the JSON
-    """
-    pass
-
-class CephArgtype(object):
-    """
-    Base class for all Ceph argument types
-
-    Instantiating an object sets any validation parameters
-    (allowable strings, numeric ranges, etc.).  The 'valid'
-    method validates a string against that initialized instance,
-    throwing ArgumentError if there's a problem.
-    """
-    def __init__(self, **kwargs):
-        """
-        set any per-instance validation parameters here
-        from kwargs (fixed string sets, integer ranges, etc)
-        """
-        pass
-
-    def valid(self, s, partial=False):
-        """
-        Run validation against given string s (generally one word);
-        partial means to accept partial string matches (begins-with).
-        If cool, set self.val to the value that should be returned
-        (a copy of the input string, or a numeric or boolean interpretation
-        thereof, for example), and return True
-        if not, throw ArgumentError(msg-as-to-why)
-        """
-        self.val = s
-        return True
-
-    def __repr__(self):
-        """
-        return string representation of description of type.  Note,
-        this is not a representation of the actual value.  Subclasses
-        probably also override __str__() to give a more user-friendly
-        'name/type' description for use in command format help messages.
-        """
-        a = ''
-        if hasattr(self, 'typeargs'):
-            a = self.typeargs
-        return '{0}(\'{1}\')'.format(self.__class__.__name__, a)
-
-    def __str__(self):
-        """
-        where __repr__ (ideally) returns a string that could be used to
-        reproduce the object, __str__ returns one you'd like to see in
-        print messages.  Use __str__ to format the argtype descriptor
-        as it would be useful in a command usage message.
-        """
-        return '<{0}>'.format(self.__class__.__name__)
-
-class CephInt(CephArgtype):
-    """
-    range-limited integers, [+|-][0-9]+ or 0x[0-9a-f]+
-    range: list of 1 or 2 ints, [min] or [min,max]
-    """
-    def __init__(self, range=''):
-        if range == '':
-            self.range = list()
-        else:
-            self.range = list(range.split('|'))
-            self.range = map(long, self.range)
-
-    def valid(self, s, partial=False):
-        try:
-            val = long(s)
-        except ValueError:
-            raise ArgumentValid("{0} doesn't represent an int".format(s))
-        if len(self.range) == 2:
-            if val < self.range[0] or val > self.range[1]:
-                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
-        elif len(self.range) == 1:
-            if val < self.range[0]:
-                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
-        self.val = val
-        return True
-
-    def __str__(self):
-        r = ''
-        if len(self.range) == 1:
-            r = '[{0}-]'.format(self.range[0])
-        if len(self.range) == 2:
-            r = '[{0}-{1}]'.format(self.range[0], self.range[1])
-
-        return '<int{0}>'.format(r)
-
-
-class CephFloat(CephArgtype):
-    """
-    range-limited float type
-    range: list of 1 or 2 floats, [min] or [min, max]
-    """
-    def __init__(self, range=''):
-        if range == '':
-            self.range = list()
-        else:
-            self.range = list(range.split('|'))
-            self.range = map(float, self.range)
-
-    def valid(self, s, partial=False):
-        try:
-            val = float(s)
-        except ValueError:
-            raise ArgumentValid("{0} doesn't represent a float".format(s))
-        if len(self.range) == 2:
-            if val < self.range[0] or val > self.range[1]:
-                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
-        elif len(self.range) == 1:
-            if val < self.range[0]:
-                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
-        self.val = val
-        return True
-
-    def __str__(self):
-        r = ''
-        if len(self.range) == 1:
-            r = '[{0}-]'.format(self.range[0])
-        if len(self.range) == 2:
-            r = '[{0}-{1}]'.format(self.range[0], self.range[1])
-        return '<float{0}>'.format(r)
-
-class CephString(CephArgtype):
-    """
-    String; pretty generic.
-    """
-    def __init__(self, badchars=''):
-        self.badchars = badchars
-
-    def valid(self, s, partial=False):
-        for c in self.badchars:
-            if c in s:
-                raise ArgumentFormat("bad char {0} in {1}".format(c, s))
-        self.val = s
-        return True
-
-    def __str__(self):
-        b = ''
-        if len(self.badchars):
-            b = '(without chars in {0})'.format(self.badchars)
-        return '<string{0}>'.format(b)
-
-class CephSocketpath(CephArgtype):
-    """
-    Admin socket path; check that it's readable and S_ISSOCK
-    """
-    def valid(self, s, partial=False):
-        mode = os.stat(s).st_mode
-        if not stat.S_ISSOCK(mode):
-            raise ArgumentValid('socket path {0} is not a socket'.format(s))
-        self.val = s
-        return True
-    def __str__(self):
-        return '<admin-socket-path>'
-
-class CephIPAddr(CephArgtype):
-    """
-    IP address (v4 or v6) with optional port
-    """
-    def valid(self, s, partial=False):
-        # parse off port, use socket to validate addr
-        type = 6
-        if s.startswith('['):
-            type = 6
-        elif s.find('.') != -1:
-            type = 4
-        if type == 4:
-            port = s.find(':')
-            if (port != -1):
-                a = s[:port]
-                p = s[port+1:]
-                if int(p) > 65535:
-                    raise ArgumentValid('{0}: invalid IPv4 port'.format(p))
-            else:
-                a = s
-                p = None
-            try:
-                socket.inet_pton(socket.AF_INET, a)
-            except:
-                raise ArgumentValid('{0}: invalid IPv4 address'.format(a))
-        else:
-            # v6
-            if s.startswith('['):
-                end = s.find(']')
-                if end == -1:
-                    raise ArgumentFormat('{0} missing terminating ]'.format(s))
-                if s[end+1] == ':':
-                    try:
-                        p = int(s[end+2])
-                    except:
-                        raise ArgumentValid('{0}: bad port number'.format(s))
-                a = s[1:end]
-            else:
-                a = s
-                p = None
-            try:
-                socket.inet_pton(socket.AF_INET6, a)
-            except:
-                raise ArgumentValid('{0} not valid IPv6 address'.format(s))
-        if p is not None and long(p) > 65535:
-            raise ArgumentValid("{0} not a valid port number".format(p))
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<IPaddr[:port]>'
-
-class CephEntityAddr(CephIPAddr):
-    """
-    EntityAddress, that is, IP address/nonce
-    """
-    def valid(self, s, partial=False):
-        ip, nonce = s.split('/')
-        if not super(self.__class__, self).valid(ip):
-            raise ArgumentValid('CephEntityAddr {0}: ip address invalid'.format(s))
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<EntityAddr>'
-
-class CephPoolname(CephArgtype):
-    """
-    Pool name; very little utility
-    """
-    def __str__(self):
-        return '<poolname>'
-
-class CephObjectname(CephArgtype):
-    """
-    Object name.  Maybe should be combined with Pool name as they're always
-    present in pairs, and then could be checked for presence
-    """
-    def valid(self, s, partial=False):
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<objectname>'
-
-class CephPgid(CephArgtype):
-    """
-    pgid, in form N.xxx (N = pool number, xxx = hex pgnum)
-    """
-    def valid(self, s, partial=False):
-        if s.find('.') == -1:
-            raise ArgumentFormat('pgid has no .')
-        poolid, pgnum = s.split('.')
-        if poolid < 0:
-            raise ArgumentFormat('pool {0} < 0'.format(poolid))
-        try:
-            pgnum = int(pgnum, 16)
-        except:
-            raise ArgumentFormat('pgnum {0} not hex integer'.format(pgnum))
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<pgid>'
-
-class CephName(CephArgtype):
-    """
-    Name, or type.id, where type is osd|mon|client|mds, and id is a base10 int,
-    or just id.
-
-    Also accept '*'
-    """
-    def valid(self, s, partial=False):
-        if s == '*':
-            self.val = s
-            self.nametype = None
-            self.nameid = None
-            return True
-        if s.find('.') == -1:
-            i = s
-        else:
-            t, i = s.split('.')
-            if not t in ('osd', 'mon', 'client', 'mds'):
-                raise ArgumentValid('unknown type ' + self.t)
-            if t == 'osd':
-                try:
-                    i = int(i)
-                except:
-                    raise ArgumentFormat('osd id ' + i + ' not integer')
-            self.nametype = t
-        self.val = s
-        self.nameid = i
-        return True
-
-    def __str__(self):
-        return '<name (type.id)>'
-
-
-class CephChoices(CephArgtype):
-    """
-    Set of string literals; init with valid choices
-    """
-    def __init__(self, strings='', **kwargs):
-        self.strings=strings.split('|')
-
-    def valid(self, s, partial=False):
-        if not partial:
-            if not s in self.strings:
-                # show as __str__ does: {s1|s2..}
-                raise ArgumentValid("{0} not in {1}".format(s, self))
-            self.val = s
-            return True
-
-        # partial
-        for t in self.strings:
-            if t.startswith(s):
-                self.val = s
-                return True
-        raise ArgumentValid("{0} not in {1}".  format(s, self))
-
-    def __str__(self):
-        if len(self.strings) == 1:
-            return '{0}'.format(self.strings[0])
-        else:
-            return '{0}'.format('|'.join(self.strings))
-
-class CephFilepath(CephArgtype):
-    """
-    Openable file
-    """
-    def valid(self, s, partial=False):
-        try:
-            f = open(s, 'a+')
-        except Exception as e:
-            raise ArgumentValid('can\'t open {0}: {1}'.format(s, e))
-        f.close()
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<outfilename>'
-
-class CephFragment(CephArgtype):
-    """
-    'Fragment' ??? XXX
-    """
-    def valid(self, s, partial=False):
-        if s.find('/') == -1:
-            raise ArgumentFormat('{0}: no /'.format(s))
-        val, bits = s.split('/')
-        # XXX is this right?
-        if not val.startswith('0x'):
-            raise ArgumentFormat("{0} not a hex integer".format(val))
-        try:
-            long(val)
-        except:
-            raise ArgumentFormat('can\'t convert {0} to integer'.format(val))
-        try:
-            long(bits)
-        except:
-            raise ArgumentFormat('can\'t convert {0} to integer'.format(bits))
-        self.val = s
-        return True
-
-    def __str__(self):
-        return "<CephFS fragment ID (0xvvv/bbb)>"
-
-
-class CephUUID(CephArgtype):
-    """
-    CephUUID: pretty self-explanatory
-    """
-    def valid(self, s, partial=False):
-        try:
-            uuid.UUID(s)
-        except Exception as e:
-            raise ArgumentFormat('invalid UUID {0}: {1}'.format(s, e))
-        self.val = s
-        return True
-
-    def __str__(self):
-        return '<uuid>'
-
-
-class CephPrefix(CephArgtype):
-    """
-    CephPrefix: magic type for "all the first n fixed strings"
-    """
-    def __init__(self, prefix=''):
-        self.prefix = prefix
-
-    def valid(self, s, partial=False):
-        if partial:
-            if self.prefix.startswith(s):
-                self.val = s
-                return True
-        else:
-            if (s == self.prefix):
-                self.val = s
-                return True
-        raise ArgumentPrefix("no match for {0}".format(s))
-
-    def __str__(self):
-        return self.prefix
-
-############################################################################
-
-
-class argdesc(object):
-    """
-    argdesc(typename, name='name', n=numallowed|N,
-            req=False, helptext=helptext, **kwargs (type-specific))
-
-    validation rules:
-    typename: type(**kwargs) will be constructed
-    later, type.valid(w) will be called with a word in that position
-
-    name is used for parse errors and for constructing JSON output
-    n is a numeric literal or 'n|N', meaning "at least one, but maybe more"
-    req=False means the argument need not be present in the list
-    helptext is the associated help for the command
-    anything else are arguments to pass to the type constructor.
-
-    self.instance is an instance of type t constructed with typeargs.
-
-    valid() will later be called with input to validate against it,
-    and will store the validated value in self.instance.val for extraction.
-    """
-    def __init__(self, t, name=None, n=1, req=True, **kwargs):
-        if isinstance(t, types.StringTypes):
-            self.t = CephPrefix
-            self.typeargs = {'prefix':t}
-            self.req = True
-        else:
-            self.t = t
-            self.typeargs = kwargs
-            self.req = bool(req == True or req == 'True')
-
-        self.name = name
-        self.N = (n in ['n', 'N'])
-        if self.N:
-            self.n = 1
-        else:
-            self.n = int(n)
-        self.instance = self.t(**self.typeargs)
-
-    def __repr__(self):
-        r = 'argdesc(' + str(self.t) + ', '
-        internals = ['N', 'typeargs', 'instance', 't']
-        for (k,v) in self.__dict__.iteritems():
-            if k.startswith('__') or k in internals:
-                pass
-            else:
-                # undo mods above
-                if k == 'n' and self.N:
-                    v = 'N'
-                r += '{0}={1}, '.format(k,v)
-        for (k,v) in self.typeargs.iteritems():
-                r += '{0}={1}, '.format(k,v)
-        return r[:-2] + ')'
-
-    def __str__(self):
-        if ((self.t == CephChoices and len(self.instance.strings) == 1)
-            or (self.t == CephPrefix)):
-            s = '{0}'.format(str(self.instance))
-        else:
-            s = '{0}({1})'.format(self.name, str(self.instance))
-            if self.N:
-                s += ' [' + str(self.instance) + '...]'
-        if not self.req:
-            s = '{' + s + '}'
-        return s
-
-    def helpstr(self):
-        """
-        like str(), but omit parameter names (except for CephString,
-        which really needs them)
-        """
-        if self.t == CephString:
-            chunk = '<{0}>'.format(self.name)
-        else:
-            chunk = str(self.instance)
-        s = '{0}'.format(chunk)
-        if self.N:
-            s += ' [' + chunk + '...]'
-        if not self.req:
-            s = '{' + s + '}'
-        return s
-
-def concise_sig(sig):
-    """
-    Return string representation of sig useful for syntax reference in help
-    """
-    first = True
-    s = ''
-    for d in sig:
-        if first:
-            first = False
-        else:
-            s += ' '
-        s += d.helpstr()
-    return s
-
-def parse_funcsig(sig):
-    """
-    parse a single descriptor (array of strings or dicts) into a
-    dict of function descriptor/validators (objects of CephXXX type)
-    """
-    newsig = []
-    argnum = 0
-    for desc in sig:
-        argnum += 1
-        if isinstance(desc, types.StringTypes):
-            t = CephPrefix
-            desc = {'type':t, 'name':'prefix', 'prefix':desc}
-        else:
-            # not a simple string, must be dict
-            if not 'type' in desc:
-                s = 'JSON descriptor {0} has no type'.format(sig)
-                raise JsonFormat(s)
-            # look up type string in our globals() dict; if it's an
-            # object of type types.TypeType, it must be a
-            # locally-defined class. otherwise, we haven't a clue.
-            if desc['type'] in globals():
-                t = globals()[desc['type']]
-                if type(t) != types.TypeType:
-                    s = 'unknown type {0}'.format(desc['type'])
-                    raise JsonFormat(s)
-            else:
-                s = 'unknown type {0}'.format(desc['type'])
-                raise JsonFormat(s)
-
-        kwargs = dict()
-        for key, val in desc.items():
-            if key not in ['type', 'name', 'n', 'req']:
-                kwargs[key] = val
-        newsig.append(argdesc(t,
-                              name=desc.get('name', None),
-                              n=desc.get('n', 1),
-                              req=desc.get('req', True),
-                              **kwargs))
-    return newsig
-
-
-def parse_json_funcsigs(s):
-    """
-    parse_json_funcsigs(s)
-
-    A function signature is mostly an array of argdesc; it's represented
-    in JSON as
-    {
-      "cmd001": {"sig":[ "type": type, "name": name, "n": num, "req":true|false <other param>], "help":helptext}
-       .
-       .
-       .
-      ]
-
-    A set of sigs is in an dict mapped by a unique number:
-    {
-      "cmd1": {
-         "sig": ["type.. ], "help":{"text":helptext}
-      }
-      "cmd2"{
-         "sig": [.. ], "help":{"text":helptext}
-      }
-    }
-
-    Parse the string s and return an dict of dicts, keyed by opcode;
-    each dict contains 'sig' with the array of descriptors, and 'help'
-    with the helptext.
-    """
-    try:
-        overall = json.loads(s)
-    except Exception as e:
-        print >> sys.stderr, "Couldn't parse JSON {0}: {1}".format(s, e)
-        raise e
-    sigdict = {}
-    for cmdtag, cmd in overall.iteritems():
-        helptext = cmd.get('help', 'no help available')
-        try:
-            sig = cmd['sig']
-        except KeyError:
-            s = "JSON descriptor {0} has no 'sig'".format(cmdtag)
-            raise JsonFormat(s)
-        newsig = parse_funcsig(sig)
-        sigdict[cmdtag] = {'sig':newsig, 'helptext':helptext}
-    return sigdict
-
-def validate_one(word, desc, partial=False):
-    """
-    validate_one(word, desc, partial=False)
-
-    validate word against the constructed instance of the type
-    in desc.  May raise exception.  If it returns false (and doesn't
-    raise an exception), desc.instance.val will
-    contain the validated value (in the appropriate type).
-    """
-    if desc.instance.valid(word, partial):
-        desc.numseen += 1
-        if desc.N:
-            desc.n = desc.numseen + 1
-        return True
-    return False
-
-def matchnum(args, signature, partial=False):
-    """
-    matchnum(s, signature, partial=False)
-
-    Returns number of arguments matched in s against signature.
-    Can be used to determine most-likely command for full or partial
-    matches (partial applies to string matches).
-    """
-    words = args[:]
-    mysig = copy.deepcopy(signature)
-    matchcnt = 0
-    for desc in mysig:
-        setattr(desc, 'numseen', 0)
-        while desc.numseen < desc.n:
-            # if there are no more arguments, return
-            if not words:
-                return matchcnt;
-            word = words.pop(0)
-            try:
-                validate_one(word, desc, partial)
-            except:
-                if not desc.req:
-                    # this wasn't required, so word may match the next desc
-                    words.insert(0, word)
-                    break
-                else:
-                    # it was required, and didn't match, return
-                    return matchcnt
-        if desc.req:
-            matchcnt += 1
-    return matchcnt
-
-def validate(args, signature, partial=False):
-    """
-    validate(s, signature, partial=False)
-
-    Assumes s represents a possible command input following format of
-    signature.  Runs a validation; no exception means it's OK.  Return
-    a dict containing all arguments named by their descriptor name
-    (with duplicate args per name accumulated into a space-separated
-    value).
-
-    If partial is set, allow partial matching (with partial dict returned)
-    """
-    words = args[:]
-    mysig = copy.deepcopy(signature)
-    d = dict()
-    for desc in mysig:
-        setattr(desc, 'numseen', 0)
-        while desc.numseen < desc.n:
-            if words:
-                word = words.pop(0)
-            else:
-                if desc.req:
-                    if desc.N and desc.numseen < 1:
-                        # wanted N, didn't even get 1
-                        if partial:
-                            return d
-                        raise ArgumentNumber('saw {0} of {1}, expected at least 1'.format(desc.numseen, desc))
-                    elif not desc.N and desc.numseen < desc.n:
-                        # wanted n, got too few
-                        if partial:
-                            return d
-                        raise ArgumentNumber('saw {0} of {1}, expected {2}'.format(desc.numseen, desc, desc.n))
-                break
-            try:
-                validate_one(word, desc)
-            except Exception as e:
-                # not valid; if not required, just push back for the next one
-                if not desc.req:
-                    words.insert(0, word)
-                    break
-                else:
-                    # hm, but it was required, so quit
-                    if partial:
-                        return d
-                    raise e
-
-            if desc.N:
-                # value should be a list
-                if desc.name in d:
-                    d[desc.name] += [desc.instance.val]
-                else:
-                    d[desc.name] = [desc.instance.val]
-            elif (desc.t == CephPrefix) and (desc.name in d):
-                # value should be a space-joined concatenation
-                d[desc.name] += ' ' + desc.instance.val
-            else:
-                # if first CephPrefix or any other type, just set it
-                d[desc.name] = desc.instance.val
-    return d
-
-def osdids():
-    ret, outbuf, outs = json_command(prefix='osd ls')
-    if ret:
-        raise RuntimeError('Can\'t contact mon for osd list')
-    return [i for i in outbuf.split('\n')]
-
-def monids():
-    ret, outbuf, outs = json_command(prefix='mon dump',
-                                     argdict={'format':'json'})
-    if ret:
-        raise RuntimeError('Can\'t contact mon for mon list')
-    d = json.loads(outbuf)
-    return [m['name'] for m in d['mons']]
-
-def mdsids():
-    ret, outbuf, outs = json_command(prefix='mds dump',
-                                     argdict={'format':'json'})
-    if ret:
-        raise RuntimeError('Can\'t contact mon for mds list')
-    d = json.loads(outbuf)
-    l = []
-    infodict = d['info']
-    for mdsdict in infodict.values():
-        l.append(mdsdict['name'])
-    return l
-
-def parse_cmdargs(args=None, target=''):
-    # alias: let the line-wrapping be sane
-    AP = argparse.ArgumentParser
-
-    # format our own help
-    parser = AP(description='Frontend for ceph CLI', add_help=False)
-
-    parser.add_argument('--completion', action='store_true')
-
-    parser.add_argument('-h', '--help', help='request mon help',
-                        action='store_true')
-    parser.add_argument('--help-all', help='request help for all daemons',
-                        action='store_true')
-
-    parser.add_argument('-c', '--conf', dest='cephconf',
-                        help='ceph configuration file')
-    parser.add_argument('-i', '--in-file', dest='input_file',
-                        help='input file')
-    parser.add_argument('-o', '--out-file', dest='output_file',
-                        help='output file')
-    parser.add_argument('-k', '--keyring', dest='keyring_file',
-                        help='keyring file')
-
-    parser.add_argument('--id', '--user', dest='client_id',
-                        help='client id for authentication')
-    parser.add_argument('--name', '-n', dest='client_name',
-                        help='client name for authentication')
-    parser.add_argument('--cluster', help='cluster name')
-
-    parser.add_argument('--admin-daemon', dest='admin_socket',
-                        help='submit admin-socket commands (\"help\" for help')
-
-    parser.add_argument('-s', '--status', action='store_true',
-                        help='show cluster status')
-
-    parser.add_argument('-w', '--watch', action='store_true',
-                        help='watch live cluster changes')
-    parser.add_argument('--watch-debug', action='store_true',
-                        help='watch debug events')
-    parser.add_argument('--watch-info', action='store_true',
-                        help='watch info events')
-    parser.add_argument('--watch-sec', action='store_true',
-                        help='watch security events')
-    parser.add_argument('--watch-warn', action='store_true',
-                        help='watch warn events')
-    parser.add_argument('--watch-error', action='store_true',
-                        help='watch error events')
-
-    parser.add_argument('--verbose', action="store_true")
-    parser.add_argument('--concise', dest='verbose', action="store_false")
-
-    parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
-                        'xml', 'xml-pretty', 'plain'], dest='output_format')
-    # for pg dump_stuck
-    parser.add_argument('--threshold', type=int, help='number of seconds for a pg to be considered stuck for pg dump_stuck')
-
-    # returns a Namespace with the parsed args, and a list of all extras
-    parsed_args, extras = parser.parse_known_args(args)
-
-    return parser, parsed_args, extras
-
-def do_help(parser, help_all = False):
-    """
-    Print basic parser help
-    If the cluster is available:
-        get and print monitor help; 
-        if help_all, print help for daemon commands as well 
-    """
-
-    def help_for_target(target):
-        ret, outbuf, outs = json_command(target=target,
-                                         prefix='get_command_descriptions', 
-                                         timeout=10)
-        if ret:
-            print >> sys.stderr, \
-                "couldn't get command descriptions for {0}: {1}".\
-                format(target, outs)
-        else:
-            sys.stdout.write(format_help(parse_json_funcsigs(outbuf)))
-
-    parser.print_help()
-    print '\n'
-    if (cluster_handle):
-        help_for_target(target=('mon', ''))
-
-    if help_all and cluster_handle:
-        # try/except in case there are no daemons of that type
-        try:
-            firstosd = osdids()[0]
-            print '\nOSD.{0} tell commands and pg pgid commands:\n\n'.\
-                format(firstosd)
-            help_for_target(target=('osd', osdids()[0]))
-
-            print '\nOSD daemon commands:\n\n'
-            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'osd.' + firstosd), ['get_command_descriptions']))))
-        except:
-            pass
-
-        try:
-            firstmon = monids()[0]
-            print '\nmon.{0} daemon commands:\n\n'.format(firstmon)
-            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mon.' + firstmon), ['get_command_descriptions']))))
-        except:
-            pass
-
-        try:
-            firstmds = mdsids()[0]
-            print '\nmds.{0} daemon commands:\n\n'.format(firstmds)
-            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mds.' + firstmds), ['get_command_descriptions']))))
-        except:
-            pass
-
-    return 0
-
-
-def descsort(sh1, sh2):
-    """
-    sort descriptors by prefixes, defined as the concatenation of all simple
-    strings in the descriptor; this works out to just the leading strings.
-    """
-    return cmp(concise_sig(sh1['sig']), concise_sig(sh2['sig']))
-
-
-dontsplit = string.letters + '{[<>]}'
-
-def wrap(s, width, indent):
-    """
-    generator to transform s into a sequence of strings width or shorter,
-    for wrapping text to a specific column width.
-    Attempt to break on anything but dontsplit characters.
-    indent is amount to indent 2nd-through-nth lines.
-
-    so "long string long string long string" width=11 indent=1 becomes
-    'long string', ' long string', ' long string' so that it can be printed
-    as
-    long string
-     long string
-     long string
-
-    Consumes s.
-    """
-    result = ''
-    leader = ''
-    while len(s):
-
-        if (len(s) <= width):
-            # no splitting; just possibly indent
-            result = leader + s
-            s = ''
-            yield result
-
-        else:
-            splitpos = width
-            while (splitpos > 0) and (s[splitpos-1] in dontsplit):
-                splitpos -= 1
-
-            if splitpos == 0:
-                splitpos = width
-
-            if result:
-                # prior result means we're mid-iteration, indent
-                result = leader
-            else:
-                # first time, set leader and width for next
-                leader = ' ' * indent
-                width -= 1      # for subsequent space additions
-
-            # remove any leading spaces in this chunk of s
-            result += s[:splitpos].lstrip()
-            s = s[splitpos:]
-
-            yield result
-
-    raise StopIteration
-
-def format_help(cmddict):
-    """
-    Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
-    cmdsig 2-column display, with each column wrapped and indented to
-    fit into 40 characters.
-    """
-
-    fullusage = ''
-    for cmd in sorted(cmddict.itervalues(), cmp=descsort):
-
-        if not cmd['helptext']:
-            continue
-        siglines = [l for l in wrap(concise_sig(cmd['sig']), 40, 1)]
-        helplines = [l for l in wrap(cmd['helptext'], 39, 1)]
-
-        # make lists the same length
-        maxlen = max(len(siglines), len(helplines))
-        siglines.extend([''] * (maxlen - len(siglines)))
-        helplines.extend([''] * (maxlen - len(helplines)))
-
-        # so we can zip them for output
-        for (s, h) in zip(siglines, helplines):
-            fullusage += '{0:40s} {1}\n'.format(s, h)
-
-    return fullusage
-
-def validate_command(parsed_args, sigdict, args):
-    """
-    turn args into a valid dictionary ready to be sent off as JSON,
-    validated against sigdict.
-    parsed_args is the namespace back from argparse
-    """
-    found = []
-    valid_dict = {}
-    if args:
-        # Repulsive hack to handle tell: lop off 'tell' and target
-        # and validate the rest of the command.  'target' is already
-        # determined in our callers, so it's ok to remove it here.
-        if args[0] == 'tell':
-            args = args[2:]
-        # look for best match, accumulate possibles in bestcmds
-        # (so we can maybe give a more-useful error message)
-        best_match_cnt = 0
-        bestcmds = []
-        for cmdtag, cmd in sigdict.iteritems():
-            sig = cmd['sig']
-            matched = matchnum(args, sig, partial=True)
-            if (matched > best_match_cnt):
-                if verbose:
-                    print >> sys.stderr, \
-                        "better match: {0} > {1}: {2}:{3} ".format(matched,
-                                      best_match_cnt, cmdtag, concise_sig(sig))
-                best_match_cnt = matched
-                bestcmds = [{cmdtag:cmd}]
-            elif matched == best_match_cnt:
-                if verbose:
-                    print >> sys.stderr, \
-                        "equal match: {0} > {1}: {2}:{3} ".format(matched,
-                                      best_match_cnt, cmdtag, concise_sig(sig))
-                bestcmds.append({cmdtag:cmd})
-
-        if verbose:
-            print >> sys.stderr, "bestcmds: ", bestcmds
-
-        # for everything in bestcmds, look for a true match
-        for cmdsig in bestcmds:
-            for cmd in cmdsig.itervalues():
-                sig = cmd['sig']
-                helptext = cmd['helptext']
-                try:
-                    valid_dict = validate(args, sig)
-                    found = sig
-                    break
-                except ArgumentPrefix:
-                    # this means a CephPrefix type didn't match; since
-                    # this is common, just eat it
-                    pass
-                except ArgumentError as e:
-                    # prefixes matched, but some other arg didn't;
-                    # this is interesting information
-                    print >> sys.stderr, '{0}: invalid command'.\
-                        format(' '.join(args))
-                    print >> sys.stderr, '{0}'.format(e)
-                    print >> sys.stderr, "did you mean {0}?\n\t{1}".\
-                        format(concise_sig(sig), helptext)
-                    pass
-
-        if not found:
-            print >> sys.stderr, 'no valid command found'
-            print >> sys.stderr, 'close matches:'
-            for cmdsig in bestcmds:
-                for (cmdtag, cmd) in cmdsig.iteritems():
-                    print >> sys.stderr, concise_sig(cmd['sig'])
-            return None
-
-        if parsed_args.output_format:
-            valid_dict['format'] = parsed_args.output_format
-
-        if parsed_args.threshold:
-            valid_dict['threshold'] = parsed_args.threshold
-
-        return valid_dict
-
-def json_command(target=('mon', ''), prefix=None, argdict=None, inbuf='',
-                 timeout=0):
-    """
-    Send a new_style command to a daemon using librados's
-    mon_command, osd_command, or pg_command.  Prefix may be supplied
-    separately or in argdict.  Any bulk input data comes in inbuf.
-    Returns (ret, outbuf, outs); ret is the return code, outbuf is
-    the outbl "bulk useful output" buffer, and outs is any status
-    or error message (intended for stderr).
-
-    If target is osd.N, send command to that osd (except for pgid cmds)
-    """
-    cmddict = {}
-    if prefix:
-        cmddict.update({'prefix':prefix})
-    if argdict:
-        cmddict.update(argdict)
-
-    # grab prefix for error messages
-    prefix = cmddict['prefix']
-
-    try:
-        if target[0] == 'osd':
-            osdtarg = CephName()
-            if 'target' in cmddict:
-                osdtarget = cmddict.pop('target')
-            else:
-                osdtarget = '{0}.{1}'.format(*target)
-
-            try:
-                osdtarg.valid(osdtarget)
-                osdid = osdtarg.nameid
-            except:
-                # uh..I dunno, try osd.0?
-                osdid = 0
-
-            if verbose:
-                print >> sys.stderr, 'submit {0} to osd.{1}'.\
-                    format(json.dumps(cmddict), osdid)
-            ret, outbuf, outs = \
-                cluster_handle.osd_command(osdid, json.dumps(cmddict), inbuf,
-                                           timeout)
-
-        elif target[0] == 'pg':
-            # leave it in cmddict for the OSD to use too
-            pgid = target[1]
-            if verbose:
-                print >> sys.stderr, 'submit {0} for pgid {1}'.\
-                    format(json.dumps(cmddict), pgid)
-            ret, outbuf, outs = \
-                cluster_handle.pg_command(pgid, json.dumps(cmddict), inbuf,
-                                          timeout)
-
-        elif target[0] == 'mon':
-            if verbose:
-                print >> sys.stderr, '{0} to {1}'.\
-                    format(json.dumps(cmddict), target[0])
-            ret, outbuf, outs = cluster_handle.mon_command(json.dumps(cmddict),
-                                                           inbuf, timeout)
-
-    except Exception as e:
-        raise RuntimeError('"{0}": exception {1}'.format(prefix, e))
-
-    return ret, outbuf, outs
-
-def admin_socket(asok_path, cmd):
-    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-    try:
-        sock.connect(asok_path)
-        sock.sendall(' '.join(cmd) + '\0')
-
-        len_str = sock.recv(4)
-        l, = struct.unpack(">I", len_str)
-        ret = ''
-
-        got = 0
-        while got < l:
-            bit = sock.recv(l - got)
-            ret += bit
-            got += len(bit)
-
-    except Exception as e:
-        raise RuntimeError('exception: {0}'.format(e))
-
-    return ret
-
-
-def ceph_conf(field, name):
-    p = subprocess.Popen(
-        args=[
-            'ceph-conf',
-            field,
-            '-n',
-            name,
-            ],
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE)
-    outdata, errdata = p.communicate()
-    if (len(errdata)):
-        raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
-    return outdata.rstrip()
-
-def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
-    """
-    Do new-style command dance.
-    target: daemon to receive command: mon (any) or osd.N
-    sigdict - the parsed output from the new monitor describing commands
-    inbuf - any -i input file data
-    verbose - bool
-    """
-    if verbose:
-        for cmdtag in sorted(sigdict.keys()):
-            cmd = sigdict[cmdtag]
-            sig = cmd['sig']
-            print '{0}: {1}'.format(cmdtag, concise_sig(sig))
-
-    got_command = False
-
-    if not got_command:
-        if cmdargs:
-            # Validate input args against list of sigs
-            valid_dict = validate_command(parsed_args, sigdict, cmdargs)
-            if valid_dict:
-                got_command = True
-            else:
-                return -errno.EINVAL, '', 'invalid command'
-        else:
-            # do the command-interpreter looping
-            while True:
-                interactive_input = raw_input('ceph> ')
-                if interactive_input in ['q', 'quit', 'Q']:
-                    return 0, '', ''
-                cmdargs = parse_cmdargs(interactive_input.split())[2]
-                target = find_cmd_target(cmdargs)
-                valid_dict = validate_command(parsed_args, sigdict, cmdargs)
-                if valid_dict:
-                    if verbose:
-                        print >> sys.stderr, "Submitting command ", valid_dict
-                    ret, outbuf, outs = json_command(target=target,
-                                                     argdict=valid_dict)
-                    if ret:
-                        sys.stderr.write('Error {0}: {1}'.format(ret, outs))
-                        return ret, '', outs
-                else:
-                    print "invalid command"
-
-    if verbose:
-        print >> sys.stderr, "Submitting command ", valid_dict
-    return json_command(target=target, argdict=valid_dict, inbuf=inbuf)
-
-OSD_TELL_MATCH = { \
-    'sig': ['tell', {'name':'target','type':'CephName'}], \
-    'matchnum': 2, \
-    'return_key': 'target', \
-}
-PGID_MATCH = { \
-    'sig': ['pg', {'name':'pgid','type':'CephPgid'}], \
-    'matchnum': 2, \
-    'return_key': 'pgid', \
-}
-
-def find_cmd_target(childargs):
-    """
-    Using a minimal validation, figure out whether the command
-    should be sent to a monitor or an osd.  We do this before even
-    asking for the 'real' set of command signatures, so we can ask the
-    right daemon.
-    Returns ('osd', osdid), ('pg', pgid), or ('mon', '')
-    """
-    sig = parse_funcsig(['tell', {'name':'target','type':'CephName'}])
-    valid_dict = validate(childargs, sig, partial=True);
-    if len(valid_dict) == 2:
-        name = CephName()
-        name.valid(valid_dict['target'])
-        return 'osd', name.nameid
-
-    sig = parse_funcsig(['pg', {'name':'pgid','type':'CephPgid'}])
-    valid_dict = validate(childargs, sig, partial=True);
-    if len(valid_dict) == 2:
-        return 'pg', valid_dict['pgid']
-
-    return 'mon', ''
-
-def complete(sigdict, args, target):
-    """
-    Command completion.  Match as much of [args] as possible,
-    and print every possible match separated by newlines. 
-    Return exitcode.
-    """
-    # XXX this looks a lot like the front of validate_command().  Refactor?
-
-    complete_verbose = 'COMPVERBOSE' in os.environ
-
-    # Repulsive hack to handle tell: lop off 'tell' and target
-    # and validate the rest of the command.  'target' is already
-    # determined in our callers, so it's ok to remove it here.
-    if len(args) and args[0] == 'tell':
-        args = args[2:]
-    # look for best match, accumulate possibles in bestcmds
-    # (so we can maybe give a more-useful error message)
-    best_match_cnt = 0
-    bestcmds = []
-    for cmdtag, cmd in sigdict.iteritems():
-        sig = cmd['sig']
-        matched = matchnum(args, sig, partial=True)
-        if (matched > best_match_cnt):
-            if complete_verbose:
-                print >> sys.stderr, \
-                    "better match: {0} > {1}: {2}:{3} ".format(matched,
-                                  best_match_cnt, cmdtag, concise_sig(sig))
-            best_match_cnt = matched
-            bestcmds = [{cmdtag:cmd}]
-        elif matched == best_match_cnt:
-            if complete_verbose:
-                print >> sys.stderr, \
-                    "equal match: {0} > {1}: {2}:{3} ".format(matched,
-                                  best_match_cnt, cmdtag, concise_sig(sig))
-            bestcmds.append({cmdtag:cmd})
-
-    # look through all matching sigs
-    comps = []
-    for cmddict in bestcmds:
-        for cmd in cmddict.itervalues():
-            sig = cmd['sig']
-            # either:
-            #   we match everything fully, so we want the next desc, or
-            #   we match more partially, so we want the partial match
-            fullindex = matchnum(args, sig, partial=False) - 1
-            partindex = matchnum(args, sig, partial=True) - 1
-            if complete_verbose:
-                print >> sys.stderr, '{}: f {} p {} len {}'.format(sig, fullindex, partindex, len(sig))
-            if fullindex == partindex and fullindex + 1 < len(sig):
-                d = sig[fullindex + 1]
-            else:
-                d = sig[partindex]
-            comps.append(str(d))
-    if complete_verbose:
-        print >> sys.stderr, '\n'.join(comps)
-    print '\n'.join(comps)
-
-    return 0
-
-###
-# main
-###
-
-def main():
-
-    parser, parsed_args, childargs = parse_cmdargs()
-
-    global verbose
-    verbose = parsed_args.verbose
-
-    # pass on --id, --name, --conf
-    name = 'client.admin'
-    if parsed_args.client_id:
-        name = 'client.' + parsed_args.client_id
-    if parsed_args.client_name:
-        name = parsed_args.client_name
-
-    # default '' means default conf search
-    conffile = ''
-    if parsed_args.cephconf:
-        conffile = parsed_args.cephconf
-    # For now, --admin-daemon is handled as usual.  Try it
-    # first in case we can't connect() to the cluster
-    if parsed_args.admin_socket:
-        try:
-            print admin_socket(parsed_args.admin_socket, childargs)
-        except Exception as e:
-            print >> sys.stderr, 'admin_socket: {0}'.format(e)
-        return 0
-
-    if len(childargs) > 0 and childargs[0] == "daemon":
-        if len(childargs) > 2:
-            if childargs[1].find('/') >= 0:
-                try:
-                    print admin_socket(childargs[1], childargs[2:])
-                except Exception as e:
-                    print >> sys.stderr, 'admin_socket: {0}'.format(e)
-                return 0
-            else:
-                # try resolve daemon name
-                path = ceph_conf('admin_socket', childargs[1])
-                try:
-                    print admin_socket(path, childargs[2:])
-                except Exception as e:
-                    print >> sys.stderr, 'admin_socket: {0}'.format(e)
-                return 0
-        else:
-            print >> sys.stderr, 'Daemon requires at least 2 arguments'
-            return 1
-
-    # handle any 'generic' ceph arguments that we didn't parse here
-    global cluster_handle
-
-    # rados.Rados() will call rados_create2, and then read the conf file,
-    # and then set the keys from the dict.  So we must do these
-    # "pre-file defaults" first (see common_preinit in librados)
-    conf_defaults = {
-        'log_to_stderr':'true',
-        'err_to_stderr':'true',
-        'log_flush_on_exit':'true',
-    }
-
-    clustername = 'ceph'
-    if parsed_args.cluster:
-        clustername = parsed_args.cluster
-
-    cluster_handle = rados.Rados(name=name, clustername=clustername,
-                                 conf_defaults=conf_defaults, conffile='')
-
-    retargs = cluster_handle.conf_parse_argv(childargs)
-    #tmp = childargs
-    childargs = retargs
-    if not childargs:
-        childargs = []
-
-    # -- means "stop parsing args", but we don't want to see it either
-    if '--' in childargs:
-        childargs.remove('--')
-
-    try:
-        cluster_handle.connect()
-    except KeyboardInterrupt:
-        print >> sys.stderr, 'Cluster connection aborted'
-        return 1
-    except Exception as e:
-        print >> sys.stderr, 'Error connecting to cluster: {0}'.\
-            format(e.__class__.__name__)
-        return 1
-
-    if parsed_args.help or parsed_args.help_all:
-        return do_help(parser, parsed_args.help_all)
-
-    # implement -w/--watch_*
-    # This is ugly, but Namespace() isn't quite rich enough.
-    level = ''
-    for k,v in parsed_args._get_kwargs():
-        if k.startswith('watch') and v:
-            if k == 'watch':
-                level = 'info'
-            else:
-                level = k.replace('watch_', '')
-    if level:
-
-        # an awfully simple callback
-        def watch_cb(arg, line, who, stamp_sec, stamp_nsec, seq, level, msg):
-            print line
-
-        # first do a ceph status
-        ret, outbuf, outs = json_command(prefix='status')
-        if ret:
-            print >> sys.stderr, "status query failed: ", outs
-            return ret
-        print outbuf
-
-        # this instance keeps the watch connection alive, but is
-        # otherwise unused
-        logwatch = rados.MonitorLog(cluster_handle, level, watch_cb, 0)
-        # loop forever letting watch_cb print lines
-        while True:
-            try:
-                pass
-            except KeyboardInterrupt:
-                # or until ^C, at least
-                return 0
-
-    # read input file, if any
-    inbuf = ''
-    if parsed_args.input_file:
-        try:
-            with open(parsed_args.input_file, 'r') as f:
-                inbuf = f.read()
-        except Exception as e:
-            print >> sys.stderr, 'Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e)
-            return 1
-
-    # prepare output file, if any
-    if parsed_args.output_file:
-        try:
-            outf = open(parsed_args.output_file, 'w')
-        except:
-            print >> sys.stderr, \
-                'Can\'t open output file {0}: {1}'.\
-                format(parsed_args.output_file, e)
-            return 1
-
-    # -s behaves like a command (ceph status).
-    if parsed_args.status:
-        childargs.insert(0, 'status')
-
-    target = find_cmd_target(childargs)
-
-    # fetch JSON sigs from command
-    # each line contains one command signature (a placeholder name
-    # of the form 'cmdNNN' followed by an array of argument descriptors)
-    # as part of the validated argument JSON object
-
-    ret, outbuf, outs = json_command(target=target,
-                                     prefix='get_command_descriptions')
-    if ret == -errno.EINVAL:
-        # send command to old monitor
-        if verbose:
-            print '{0} to old monitor'.format(' '.join(childargs))
-        ret, outbuf, outs = cluster_handle.mon_command(childargs, inbuf)
-    elif ret:
-        if ret < 0:
-            ret = -ret
-        print >> sys.stderr, \
-            'Problem getting command descriptions from {0}, {1}'.\
-            format(target, errno.errorcode[ret])
-        return ret
-    else:
-        sigdict = parse_json_funcsigs(outbuf)
-
-        if parsed_args.completion:
-            return complete(sigdict, childargs, target)
-
-        ret, outbuf, outs = new_style_command(parsed_args, childargs, target,
-                                              sigdict, inbuf, verbose)
-
-    if ret < 0:
-        ret = -ret
-        print >> sys.stderr, 'Error {0}: {1}'.format(errno.errorcode[ret], outs)
-        return ret
-
-    # this assumes outs never has useful command output, only status
-    if outs:
-        print >> sys.stderr, outs
-
-    if (parsed_args.output_file):
-        outf.write(outbuf)
-        outf.close()
-    else:
-        # hack: old code printed status line before many json outputs
-        # (osd dump, etc.) that consumers know to ignore.  Add blank line
-        # to satisfy consumers that skip the first line, but not annoy
-        # consumers that don't.
-        if parsed_args.output_format and \
-           parsed_args.output_format.startswith('json'):
-            sys.stdout.write('\n');
-
-        sys.stdout.write(outbuf)
-
-    return 0
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/src/ceph.in b/src/ceph.in
new file mode 100755 (executable)
index 0000000..0b345a5
--- /dev/null
@@ -0,0 +1,1508 @@
+#
+# Processed in Makefile to add python #! line and version variable
+#
+
+import argparse
+import copy
+import errno
+import json
+import os
+import rados
+# for raw_input to do readline cmd editing
+import readline
+import socket
+import stat
+import string
+import struct
+import subprocess
+import sys
+import types
+import uuid
+
+# just a couple of globals
+
+verbose = False
+cluster_handle = None
+
+class ArgumentError(Exception):
+    """
+    Something wrong with arguments
+    """
+    pass
+
+class ArgumentNumber(ArgumentError):
+    """
+    Wrong number of a repeated argument
+    """
+    pass
+
+class ArgumentFormat(ArgumentError):
+    """
+    Argument value has wrong format
+    """
+    pass
+
+class ArgumentValid(ArgumentError):
+    """
+    Argument value is otherwise invalid (doesn't match choices, for instance)
+    """
+    pass
+
+class ArgumentPrefix(ArgumentError):
+    """
+    Special for mismatched prefix; less severe, don't report by default
+    """
+    pass
+
+class JsonFormat(Exception):
+    """
+    some syntactic or semantic issue with the JSON
+    """
+    pass
+
+class CephArgtype(object):
+    """
+    Base class for all Ceph argument types
+
+    Instantiating an object sets any validation parameters
+    (allowable strings, numeric ranges, etc.).  The 'valid'
+    method validates a string against that initialized instance,
+    throwing ArgumentError if there's a problem.
+    """
+    def __init__(self, **kwargs):
+        """
+        set any per-instance validation parameters here
+        from kwargs (fixed string sets, integer ranges, etc)
+        """
+        pass
+
+    def valid(self, s, partial=False):
+        """
+        Run validation against given string s (generally one word);
+        partial means to accept partial string matches (begins-with).
+        If cool, set self.val to the value that should be returned
+        (a copy of the input string, or a numeric or boolean interpretation
+        thereof, for example), and return True
+        if not, throw ArgumentError(msg-as-to-why)
+        """
+        self.val = s
+        return True
+
+    def __repr__(self):
+        """
+        return string representation of description of type.  Note,
+        this is not a representation of the actual value.  Subclasses
+        probably also override __str__() to give a more user-friendly
+        'name/type' description for use in command format help messages.
+        """
+        a = ''
+        if hasattr(self, 'typeargs'):
+            a = self.typeargs
+        return '{0}(\'{1}\')'.format(self.__class__.__name__, a)
+
+    def __str__(self):
+        """
+        where __repr__ (ideally) returns a string that could be used to
+        reproduce the object, __str__ returns one you'd like to see in
+        print messages.  Use __str__ to format the argtype descriptor
+        as it would be useful in a command usage message.
+        """
+        return '<{0}>'.format(self.__class__.__name__)
+
+class CephInt(CephArgtype):
+    """
+    range-limited integers, [+|-][0-9]+ or 0x[0-9a-f]+
+    range: list of 1 or 2 ints, [min] or [min,max]
+    """
+    def __init__(self, range=''):
+        if range == '':
+            self.range = list()
+        else:
+            self.range = list(range.split('|'))
+            self.range = map(long, self.range)
+
+    def valid(self, s, partial=False):
+        try:
+            val = long(s)
+        except ValueError:
+            raise ArgumentValid("{0} doesn't represent an int".format(s))
+        if len(self.range) == 2:
+            if val < self.range[0] or val > self.range[1]:
+                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
+        elif len(self.range) == 1:
+            if val < self.range[0]:
+                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
+        self.val = val
+        return True
+
+    def __str__(self):
+        r = ''
+        if len(self.range) == 1:
+            r = '[{0}-]'.format(self.range[0])
+        if len(self.range) == 2:
+            r = '[{0}-{1}]'.format(self.range[0], self.range[1])
+
+        return '<int{0}>'.format(r)
+
+
+class CephFloat(CephArgtype):
+    """
+    range-limited float type
+    range: list of 1 or 2 floats, [min] or [min, max]
+    """
+    def __init__(self, range=''):
+        if range == '':
+            self.range = list()
+        else:
+            self.range = list(range.split('|'))
+            self.range = map(float, self.range)
+
+    def valid(self, s, partial=False):
+        try:
+            val = float(s)
+        except ValueError:
+            raise ArgumentValid("{0} doesn't represent a float".format(s))
+        if len(self.range) == 2:
+            if val < self.range[0] or val > self.range[1]:
+                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
+        elif len(self.range) == 1:
+            if val < self.range[0]:
+                raise ArgumentValid("{0} not in range {1}".format(val, self.range))
+        self.val = val
+        return True
+
+    def __str__(self):
+        r = ''
+        if len(self.range) == 1:
+            r = '[{0}-]'.format(self.range[0])
+        if len(self.range) == 2:
+            r = '[{0}-{1}]'.format(self.range[0], self.range[1])
+        return '<float{0}>'.format(r)
+
+class CephString(CephArgtype):
+    """
+    String; pretty generic.
+    """
+    def __init__(self, badchars=''):
+        self.badchars = badchars
+
+    def valid(self, s, partial=False):
+        for c in self.badchars:
+            if c in s:
+                raise ArgumentFormat("bad char {0} in {1}".format(c, s))
+        self.val = s
+        return True
+
+    def __str__(self):
+        b = ''
+        if len(self.badchars):
+            b = '(without chars in {0})'.format(self.badchars)
+        return '<string{0}>'.format(b)
+
+class CephSocketpath(CephArgtype):
+    """
+    Admin socket path; check that it's readable and S_ISSOCK
+    """
+    def valid(self, s, partial=False):
+        mode = os.stat(s).st_mode
+        if not stat.S_ISSOCK(mode):
+            raise ArgumentValid('socket path {0} is not a socket'.format(s))
+        self.val = s
+        return True
+    def __str__(self):
+        return '<admin-socket-path>'
+
+class CephIPAddr(CephArgtype):
+    """
+    IP address (v4 or v6) with optional port
+    """
+    def valid(self, s, partial=False):
+        # parse off port, use socket to validate addr
+        type = 6
+        if s.startswith('['):
+            type = 6
+        elif s.find('.') != -1:
+            type = 4
+        if type == 4:
+            port = s.find(':')
+            if (port != -1):
+                a = s[:port]
+                p = s[port+1:]
+                if int(p) > 65535:
+                    raise ArgumentValid('{0}: invalid IPv4 port'.format(p))
+            else:
+                a = s
+                p = None
+            try:
+                socket.inet_pton(socket.AF_INET, a)
+            except:
+                raise ArgumentValid('{0}: invalid IPv4 address'.format(a))
+        else:
+            # v6
+            if s.startswith('['):
+                end = s.find(']')
+                if end == -1:
+                    raise ArgumentFormat('{0} missing terminating ]'.format(s))
+                if s[end+1] == ':':
+                    try:
+                        p = int(s[end+2])
+                    except:
+                        raise ArgumentValid('{0}: bad port number'.format(s))
+                a = s[1:end]
+            else:
+                a = s
+                p = None
+            try:
+                socket.inet_pton(socket.AF_INET6, a)
+            except:
+                raise ArgumentValid('{0} not valid IPv6 address'.format(s))
+        if p is not None and long(p) > 65535:
+            raise ArgumentValid("{0} not a valid port number".format(p))
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<IPaddr[:port]>'
+
+class CephEntityAddr(CephIPAddr):
+    """
+    EntityAddress, that is, IP address/nonce
+    """
+    def valid(self, s, partial=False):
+        ip, nonce = s.split('/')
+        if not super(self.__class__, self).valid(ip):
+            raise ArgumentValid('CephEntityAddr {0}: ip address invalid'.format(s))
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<EntityAddr>'
+
+class CephPoolname(CephArgtype):
+    """
+    Pool name; very little utility
+    """
+    def __str__(self):
+        return '<poolname>'
+
+class CephObjectname(CephArgtype):
+    """
+    Object name.  Maybe should be combined with Pool name as they're always
+    present in pairs, and then could be checked for presence
+    """
+    def valid(self, s, partial=False):
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<objectname>'
+
+class CephPgid(CephArgtype):
+    """
+    pgid, in form N.xxx (N = pool number, xxx = hex pgnum)
+    """
+    def valid(self, s, partial=False):
+        if s.find('.') == -1:
+            raise ArgumentFormat('pgid has no .')
+        poolid, pgnum = s.split('.')
+        if poolid < 0:
+            raise ArgumentFormat('pool {0} < 0'.format(poolid))
+        try:
+            pgnum = int(pgnum, 16)
+        except:
+            raise ArgumentFormat('pgnum {0} not hex integer'.format(pgnum))
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<pgid>'
+
+class CephName(CephArgtype):
+    """
+    Name, or type.id, where type is osd|mon|client|mds, and id is a base10 int,
+    or just id.
+
+    Also accept '*'
+    """
+    def valid(self, s, partial=False):
+        if s == '*':
+            self.val = s
+            self.nametype = None
+            self.nameid = None
+            return True
+        if s.find('.') == -1:
+            i = s
+        else:
+            t, i = s.split('.')
+            if not t in ('osd', 'mon', 'client', 'mds'):
+                raise ArgumentValid('unknown type ' + self.t)
+            if t == 'osd':
+                try:
+                    i = int(i)
+                except:
+                    raise ArgumentFormat('osd id ' + i + ' not integer')
+            self.nametype = t
+        self.val = s
+        self.nameid = i
+        return True
+
+    def __str__(self):
+        return '<name (type.id)>'
+
+
+class CephChoices(CephArgtype):
+    """
+    Set of string literals; init with valid choices
+    """
+    def __init__(self, strings='', **kwargs):
+        self.strings=strings.split('|')
+
+    def valid(self, s, partial=False):
+        if not partial:
+            if not s in self.strings:
+                # show as __str__ does: {s1|s2..}
+                raise ArgumentValid("{0} not in {1}".format(s, self))
+            self.val = s
+            return True
+
+        # partial
+        for t in self.strings:
+            if t.startswith(s):
+                self.val = s
+                return True
+        raise ArgumentValid("{0} not in {1}".  format(s, self))
+
+    def __str__(self):
+        if len(self.strings) == 1:
+            return '{0}'.format(self.strings[0])
+        else:
+            return '{0}'.format('|'.join(self.strings))
+
+class CephFilepath(CephArgtype):
+    """
+    Openable file
+    """
+    def valid(self, s, partial=False):
+        try:
+            f = open(s, 'a+')
+        except Exception as e:
+            raise ArgumentValid('can\'t open {0}: {1}'.format(s, e))
+        f.close()
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<outfilename>'
+
+class CephFragment(CephArgtype):
+    """
+    'Fragment' ??? XXX
+    """
+    def valid(self, s, partial=False):
+        if s.find('/') == -1:
+            raise ArgumentFormat('{0}: no /'.format(s))
+        val, bits = s.split('/')
+        # XXX is this right?
+        if not val.startswith('0x'):
+            raise ArgumentFormat("{0} not a hex integer".format(val))
+        try:
+            long(val)
+        except:
+            raise ArgumentFormat('can\'t convert {0} to integer'.format(val))
+        try:
+            long(bits)
+        except:
+            raise ArgumentFormat('can\'t convert {0} to integer'.format(bits))
+        self.val = s
+        return True
+
+    def __str__(self):
+        return "<CephFS fragment ID (0xvvv/bbb)>"
+
+
+class CephUUID(CephArgtype):
+    """
+    CephUUID: pretty self-explanatory
+    """
+    def valid(self, s, partial=False):
+        try:
+            uuid.UUID(s)
+        except Exception as e:
+            raise ArgumentFormat('invalid UUID {0}: {1}'.format(s, e))
+        self.val = s
+        return True
+
+    def __str__(self):
+        return '<uuid>'
+
+
+class CephPrefix(CephArgtype):
+    """
+    CephPrefix: magic type for "all the first n fixed strings"
+    """
+    def __init__(self, prefix=''):
+        self.prefix = prefix
+
+    def valid(self, s, partial=False):
+        if partial:
+            if self.prefix.startswith(s):
+                self.val = s
+                return True
+        else:
+            if (s == self.prefix):
+                self.val = s
+                return True
+        raise ArgumentPrefix("no match for {0}".format(s))
+
+    def __str__(self):
+        return self.prefix
+
+############################################################################
+
+
+class argdesc(object):
+    """
+    argdesc(typename, name='name', n=numallowed|N,
+            req=False, helptext=helptext, **kwargs (type-specific))
+
+    validation rules:
+    typename: type(**kwargs) will be constructed
+    later, type.valid(w) will be called with a word in that position
+
+    name is used for parse errors and for constructing JSON output
+    n is a numeric literal or 'n|N', meaning "at least one, but maybe more"
+    req=False means the argument need not be present in the list
+    helptext is the associated help for the command
+    anything else are arguments to pass to the type constructor.
+
+    self.instance is an instance of type t constructed with typeargs.
+
+    valid() will later be called with input to validate against it,
+    and will store the validated value in self.instance.val for extraction.
+    """
+    def __init__(self, t, name=None, n=1, req=True, **kwargs):
+        if isinstance(t, types.StringTypes):
+            self.t = CephPrefix
+            self.typeargs = {'prefix':t}
+            self.req = True
+        else:
+            self.t = t
+            self.typeargs = kwargs
+            self.req = bool(req == True or req == 'True')
+
+        self.name = name
+        self.N = (n in ['n', 'N'])
+        if self.N:
+            self.n = 1
+        else:
+            self.n = int(n)
+        self.instance = self.t(**self.typeargs)
+
+    def __repr__(self):
+        r = 'argdesc(' + str(self.t) + ', '
+        internals = ['N', 'typeargs', 'instance', 't']
+        for (k,v) in self.__dict__.iteritems():
+            if k.startswith('__') or k in internals:
+                pass
+            else:
+                # undo mods above
+                if k == 'n' and self.N:
+                    v = 'N'
+                r += '{0}={1}, '.format(k,v)
+        for (k,v) in self.typeargs.iteritems():
+                r += '{0}={1}, '.format(k,v)
+        return r[:-2] + ')'
+
+    def __str__(self):
+        if ((self.t == CephChoices and len(self.instance.strings) == 1)
+            or (self.t == CephPrefix)):
+            s = '{0}'.format(str(self.instance))
+        else:
+            s = '{0}({1})'.format(self.name, str(self.instance))
+            if self.N:
+                s += ' [' + str(self.instance) + '...]'
+        if not self.req:
+            s = '{' + s + '}'
+        return s
+
+    def helpstr(self):
+        """
+        like str(), but omit parameter names (except for CephString,
+        which really needs them)
+        """
+        if self.t == CephString:
+            chunk = '<{0}>'.format(self.name)
+        else:
+            chunk = str(self.instance)
+        s = '{0}'.format(chunk)
+        if self.N:
+            s += ' [' + chunk + '...]'
+        if not self.req:
+            s = '{' + s + '}'
+        return s
+
+def concise_sig(sig):
+    """
+    Return string representation of sig useful for syntax reference in help
+    """
+    first = True
+    s = ''
+    for d in sig:
+        if first:
+            first = False
+        else:
+            s += ' '
+        s += d.helpstr()
+    return s
+
+def parse_funcsig(sig):
+    """
+    parse a single descriptor (array of strings or dicts) into a
+    dict of function descriptor/validators (objects of CephXXX type)
+    """
+    newsig = []
+    argnum = 0
+    for desc in sig:
+        argnum += 1
+        if isinstance(desc, types.StringTypes):
+            t = CephPrefix
+            desc = {'type':t, 'name':'prefix', 'prefix':desc}
+        else:
+            # not a simple string, must be dict
+            if not 'type' in desc:
+                s = 'JSON descriptor {0} has no type'.format(sig)
+                raise JsonFormat(s)
+            # look up type string in our globals() dict; if it's an
+            # object of type types.TypeType, it must be a
+            # locally-defined class. otherwise, we haven't a clue.
+            if desc['type'] in globals():
+                t = globals()[desc['type']]
+                if type(t) != types.TypeType:
+                    s = 'unknown type {0}'.format(desc['type'])
+                    raise JsonFormat(s)
+            else:
+                s = 'unknown type {0}'.format(desc['type'])
+                raise JsonFormat(s)
+
+        kwargs = dict()
+        for key, val in desc.items():
+            if key not in ['type', 'name', 'n', 'req']:
+                kwargs[key] = val
+        newsig.append(argdesc(t,
+                              name=desc.get('name', None),
+                              n=desc.get('n', 1),
+                              req=desc.get('req', True),
+                              **kwargs))
+    return newsig
+
+
+def parse_json_funcsigs(s):
+    """
+    parse_json_funcsigs(s)
+
+    A function signature is mostly an array of argdesc; it's represented
+    in JSON as
+    {
+      "cmd001": {"sig":[ "type": type, "name": name, "n": num, "req":true|false <other param>], "help":helptext}
+       .
+       .
+       .
+      ]
+
+    A set of sigs is in an dict mapped by a unique number:
+    {
+      "cmd1": {
+         "sig": ["type.. ], "help":{"text":helptext}
+      }
+      "cmd2"{
+         "sig": [.. ], "help":{"text":helptext}
+      }
+    }
+
+    Parse the string s and return an dict of dicts, keyed by opcode;
+    each dict contains 'sig' with the array of descriptors, and 'help'
+    with the helptext.
+    """
+    try:
+        overall = json.loads(s)
+    except Exception as e:
+        print >> sys.stderr, "Couldn't parse JSON {0}: {1}".format(s, e)
+        raise e
+    sigdict = {}
+    for cmdtag, cmd in overall.iteritems():
+        helptext = cmd.get('help', 'no help available')
+        try:
+            sig = cmd['sig']
+        except KeyError:
+            s = "JSON descriptor {0} has no 'sig'".format(cmdtag)
+            raise JsonFormat(s)
+        newsig = parse_funcsig(sig)
+        sigdict[cmdtag] = {'sig':newsig, 'helptext':helptext}
+    return sigdict
+
+def validate_one(word, desc, partial=False):
+    """
+    validate_one(word, desc, partial=False)
+
+    validate word against the constructed instance of the type
+    in desc.  May raise exception.  If it returns false (and doesn't
+    raise an exception), desc.instance.val will
+    contain the validated value (in the appropriate type).
+    """
+    if desc.instance.valid(word, partial):
+        desc.numseen += 1
+        if desc.N:
+            desc.n = desc.numseen + 1
+        return True
+    return False
+
+def matchnum(args, signature, partial=False):
+    """
+    matchnum(s, signature, partial=False)
+
+    Returns number of arguments matched in s against signature.
+    Can be used to determine most-likely command for full or partial
+    matches (partial applies to string matches).
+    """
+    words = args[:]
+    mysig = copy.deepcopy(signature)
+    matchcnt = 0
+    for desc in mysig:
+        setattr(desc, 'numseen', 0)
+        while desc.numseen < desc.n:
+            # if there are no more arguments, return
+            if not words:
+                return matchcnt;
+            word = words.pop(0)
+            try:
+                validate_one(word, desc, partial)
+            except:
+                if not desc.req:
+                    # this wasn't required, so word may match the next desc
+                    words.insert(0, word)
+                    break
+                else:
+                    # it was required, and didn't match, return
+                    return matchcnt
+        if desc.req:
+            matchcnt += 1
+    return matchcnt
+
+def validate(args, signature, partial=False):
+    """
+    validate(s, signature, partial=False)
+
+    Assumes s represents a possible command input following format of
+    signature.  Runs a validation; no exception means it's OK.  Return
+    a dict containing all arguments named by their descriptor name
+    (with duplicate args per name accumulated into a space-separated
+    value).
+
+    If partial is set, allow partial matching (with partial dict returned)
+    """
+    words = args[:]
+    mysig = copy.deepcopy(signature)
+    d = dict()
+    for desc in mysig:
+        setattr(desc, 'numseen', 0)
+        while desc.numseen < desc.n:
+            if words:
+                word = words.pop(0)
+            else:
+                if desc.req:
+                    if desc.N and desc.numseen < 1:
+                        # wanted N, didn't even get 1
+                        if partial:
+                            return d
+                        raise ArgumentNumber('saw {0} of {1}, expected at least 1'.format(desc.numseen, desc))
+                    elif not desc.N and desc.numseen < desc.n:
+                        # wanted n, got too few
+                        if partial:
+                            return d
+                        raise ArgumentNumber('saw {0} of {1}, expected {2}'.format(desc.numseen, desc, desc.n))
+                break
+            try:
+                validate_one(word, desc)
+            except Exception as e:
+                # not valid; if not required, just push back for the next one
+                if not desc.req:
+                    words.insert(0, word)
+                    break
+                else:
+                    # hm, but it was required, so quit
+                    if partial:
+                        return d
+                    raise e
+
+            if desc.N:
+                # value should be a list
+                if desc.name in d:
+                    d[desc.name] += [desc.instance.val]
+                else:
+                    d[desc.name] = [desc.instance.val]
+            elif (desc.t == CephPrefix) and (desc.name in d):
+                # value should be a space-joined concatenation
+                d[desc.name] += ' ' + desc.instance.val
+            else:
+                # if first CephPrefix or any other type, just set it
+                d[desc.name] = desc.instance.val
+    return d
+
+def osdids():
+    ret, outbuf, outs = json_command(prefix='osd ls')
+    if ret:
+        raise RuntimeError('Can\'t contact mon for osd list')
+    return [i for i in outbuf.split('\n')]
+
+def monids():
+    ret, outbuf, outs = json_command(prefix='mon dump',
+                                     argdict={'format':'json'})
+    if ret:
+        raise RuntimeError('Can\'t contact mon for mon list')
+    d = json.loads(outbuf)
+    return [m['name'] for m in d['mons']]
+
+def mdsids():
+    ret, outbuf, outs = json_command(prefix='mds dump',
+                                     argdict={'format':'json'})
+    if ret:
+        raise RuntimeError('Can\'t contact mon for mds list')
+    d = json.loads(outbuf)
+    l = []
+    infodict = d['info']
+    for mdsdict in infodict.values():
+        l.append(mdsdict['name'])
+    return l
+
+def parse_cmdargs(args=None, target=''):
+    # alias: let the line-wrapping be sane
+    AP = argparse.ArgumentParser
+
+    # format our own help
+    parser = AP(description='Frontend for ceph CLI', add_help=False)
+
+    parser.add_argument('--completion', action='store_true')
+
+    parser.add_argument('-h', '--help', help='request mon help',
+                        action='store_true')
+    parser.add_argument('--help-all', help='request help for all daemons',
+                        action='store_true')
+
+    parser.add_argument('-c', '--conf', dest='cephconf',
+                        help='ceph configuration file')
+    parser.add_argument('-i', '--in-file', dest='input_file',
+                        help='input file')
+    parser.add_argument('-o', '--out-file', dest='output_file',
+                        help='output file')
+    parser.add_argument('-k', '--keyring', dest='keyring_file',
+                        help='keyring file')
+
+    parser.add_argument('--id', '--user', dest='client_id',
+                        help='client id for authentication')
+    parser.add_argument('--name', '-n', dest='client_name',
+                        help='client name for authentication')
+    parser.add_argument('--cluster', help='cluster name')
+
+    parser.add_argument('--admin-daemon', dest='admin_socket',
+                        help='submit admin-socket commands (\"help\" for help')
+
+    parser.add_argument('-s', '--status', action='store_true',
+                        help='show cluster status')
+
+    parser.add_argument('-w', '--watch', action='store_true',
+                        help='watch live cluster changes')
+    parser.add_argument('--watch-debug', action='store_true',
+                        help='watch debug events')
+    parser.add_argument('--watch-info', action='store_true',
+                        help='watch info events')
+    parser.add_argument('--watch-sec', action='store_true',
+                        help='watch security events')
+    parser.add_argument('--watch-warn', action='store_true',
+                        help='watch warn events')
+    parser.add_argument('--watch-error', action='store_true',
+                        help='watch error events')
+
+    parser.add_argument('-v', action="store_true")
+    parser.add_argument('--verbose', action="store_true")
+    parser.add_argument('--concise', dest='verbose', action="store_false")
+
+    parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
+                        'xml', 'xml-pretty', 'plain'], dest='output_format')
+    # for pg dump_stuck
+    parser.add_argument('--threshold', type=int, help='number of seconds for a pg to be considered stuck for pg dump_stuck')
+
+    # returns a Namespace with the parsed args, and a list of all extras
+    parsed_args, extras = parser.parse_known_args(args)
+
+    return parser, parsed_args, extras
+
+def do_help(parser, help_all = False):
+    """
+    Print basic parser help
+    If the cluster is available:
+        get and print monitor help; 
+        if help_all, print help for daemon commands as well 
+    """
+
+    def help_for_target(target):
+        ret, outbuf, outs = json_command(target=target,
+                                         prefix='get_command_descriptions', 
+                                         timeout=10)
+        if ret:
+            print >> sys.stderr, \
+                "couldn't get command descriptions for {0}: {1}".\
+                format(target, outs)
+        else:
+            sys.stdout.write(format_help(parse_json_funcsigs(outbuf)))
+
+    parser.print_help()
+    print '\n'
+    if (cluster_handle):
+        help_for_target(target=('mon', ''))
+
+    if help_all and cluster_handle:
+        # try/except in case there are no daemons of that type
+        try:
+            firstosd = osdids()[0]
+            print '\nOSD.{0} tell commands and pg pgid commands:\n\n'.\
+                format(firstosd)
+            help_for_target(target=('osd', osdids()[0]))
+
+            print '\nOSD daemon commands:\n\n'
+            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'osd.' + firstosd), ['get_command_descriptions']))))
+        except:
+            pass
+
+        try:
+            firstmon = monids()[0]
+            print '\nmon.{0} daemon commands:\n\n'.format(firstmon)
+            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mon.' + firstmon), ['get_command_descriptions']))))
+        except:
+            pass
+
+        try:
+            firstmds = mdsids()[0]
+            print '\nmds.{0} daemon commands:\n\n'.format(firstmds)
+            sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mds.' + firstmds), ['get_command_descriptions']))))
+        except:
+            pass
+
+    return 0
+
+
+def descsort(sh1, sh2):
+    """
+    sort descriptors by prefixes, defined as the concatenation of all simple
+    strings in the descriptor; this works out to just the leading strings.
+    """
+    return cmp(concise_sig(sh1['sig']), concise_sig(sh2['sig']))
+
+
+dontsplit = string.letters + '{[<>]}'
+
+def wrap(s, width, indent):
+    """
+    generator to transform s into a sequence of strings width or shorter,
+    for wrapping text to a specific column width.
+    Attempt to break on anything but dontsplit characters.
+    indent is amount to indent 2nd-through-nth lines.
+
+    so "long string long string long string" width=11 indent=1 becomes
+    'long string', ' long string', ' long string' so that it can be printed
+    as
+    long string
+     long string
+     long string
+
+    Consumes s.
+    """
+    result = ''
+    leader = ''
+    while len(s):
+
+        if (len(s) <= width):
+            # no splitting; just possibly indent
+            result = leader + s
+            s = ''
+            yield result
+
+        else:
+            splitpos = width
+            while (splitpos > 0) and (s[splitpos-1] in dontsplit):
+                splitpos -= 1
+
+            if splitpos == 0:
+                splitpos = width
+
+            if result:
+                # prior result means we're mid-iteration, indent
+                result = leader
+            else:
+                # first time, set leader and width for next
+                leader = ' ' * indent
+                width -= 1      # for subsequent space additions
+
+            # remove any leading spaces in this chunk of s
+            result += s[:splitpos].lstrip()
+            s = s[splitpos:]
+
+            yield result
+
+    raise StopIteration
+
+def format_help(cmddict):
+    """
+    Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
+    cmdsig 2-column display, with each column wrapped and indented to
+    fit into 40 characters.
+    """
+
+    fullusage = ''
+    for cmd in sorted(cmddict.itervalues(), cmp=descsort):
+
+        if not cmd['helptext']:
+            continue
+        siglines = [l for l in wrap(concise_sig(cmd['sig']), 40, 1)]
+        helplines = [l for l in wrap(cmd['helptext'], 39, 1)]
+
+        # make lists the same length
+        maxlen = max(len(siglines), len(helplines))
+        siglines.extend([''] * (maxlen - len(siglines)))
+        helplines.extend([''] * (maxlen - len(helplines)))
+
+        # so we can zip them for output
+        for (s, h) in zip(siglines, helplines):
+            fullusage += '{0:40s} {1}\n'.format(s, h)
+
+    return fullusage
+
+def validate_command(parsed_args, sigdict, args):
+    """
+    turn args into a valid dictionary ready to be sent off as JSON,
+    validated against sigdict.
+    parsed_args is the namespace back from argparse
+    """
+    found = []
+    valid_dict = {}
+    if args:
+        # Repulsive hack to handle tell: lop off 'tell' and target
+        # and validate the rest of the command.  'target' is already
+        # determined in our callers, so it's ok to remove it here.
+        if args[0] == 'tell':
+            args = args[2:]
+        # look for best match, accumulate possibles in bestcmds
+        # (so we can maybe give a more-useful error message)
+        best_match_cnt = 0
+        bestcmds = []
+        for cmdtag, cmd in sigdict.iteritems():
+            sig = cmd['sig']
+            matched = matchnum(args, sig, partial=True)
+            if (matched > best_match_cnt):
+                if verbose:
+                    print >> sys.stderr, \
+                        "better match: {0} > {1}: {2}:{3} ".format(matched,
+                                      best_match_cnt, cmdtag, concise_sig(sig))
+                best_match_cnt = matched
+                bestcmds = [{cmdtag:cmd}]
+            elif matched == best_match_cnt:
+                if verbose:
+                    print >> sys.stderr, \
+                        "equal match: {0} > {1}: {2}:{3} ".format(matched,
+                                      best_match_cnt, cmdtag, concise_sig(sig))
+                bestcmds.append({cmdtag:cmd})
+
+        if verbose:
+            print >> sys.stderr, "bestcmds: ", bestcmds
+
+        # for everything in bestcmds, look for a true match
+        for cmdsig in bestcmds:
+            for cmd in cmdsig.itervalues():
+                sig = cmd['sig']
+                helptext = cmd['helptext']
+                try:
+                    valid_dict = validate(args, sig)
+                    found = sig
+                    break
+                except ArgumentPrefix:
+                    # this means a CephPrefix type didn't match; since
+                    # this is common, just eat it
+                    pass
+                except ArgumentError as e:
+                    # prefixes matched, but some other arg didn't;
+                    # this is interesting information
+                    print >> sys.stderr, '{0}: invalid command'.\
+                        format(' '.join(args))
+                    print >> sys.stderr, '{0}'.format(e)
+                    print >> sys.stderr, "did you mean {0}?\n\t{1}".\
+                        format(concise_sig(sig), helptext)
+                    pass
+
+        if not found:
+            print >> sys.stderr, 'no valid command found'
+            print >> sys.stderr, 'close matches:'
+            for cmdsig in bestcmds:
+                for (cmdtag, cmd) in cmdsig.iteritems():
+                    print >> sys.stderr, concise_sig(cmd['sig'])
+            return None
+
+        if parsed_args.output_format:
+            valid_dict['format'] = parsed_args.output_format
+
+        if parsed_args.threshold:
+            valid_dict['threshold'] = parsed_args.threshold
+
+        return valid_dict
+
+def json_command(target=('mon', ''), prefix=None, argdict=None, inbuf='',
+                 timeout=0):
+    """
+    Send a new_style command to a daemon using librados's
+    mon_command, osd_command, or pg_command.  Prefix may be supplied
+    separately or in argdict.  Any bulk input data comes in inbuf.
+    Returns (ret, outbuf, outs); ret is the return code, outbuf is
+    the outbl "bulk useful output" buffer, and outs is any status
+    or error message (intended for stderr).
+
+    If target is osd.N, send command to that osd (except for pgid cmds)
+    """
+    cmddict = {}
+    if prefix:
+        cmddict.update({'prefix':prefix})
+    if argdict:
+        cmddict.update(argdict)
+
+    # grab prefix for error messages
+    prefix = cmddict['prefix']
+
+    try:
+        if target[0] == 'osd':
+            osdtarg = CephName()
+            if 'target' in cmddict:
+                osdtarget = cmddict.pop('target')
+            else:
+                osdtarget = '{0}.{1}'.format(*target)
+
+            try:
+                osdtarg.valid(osdtarget)
+                osdid = osdtarg.nameid
+            except:
+                # uh..I dunno, try osd.0?
+                osdid = 0
+
+            if verbose:
+                print >> sys.stderr, 'submit {0} to osd.{1}'.\
+                    format(json.dumps(cmddict), osdid)
+            ret, outbuf, outs = \
+                cluster_handle.osd_command(osdid, json.dumps(cmddict), inbuf,
+                                           timeout)
+
+        elif target[0] == 'pg':
+            # leave it in cmddict for the OSD to use too
+            pgid = target[1]
+            if verbose:
+                print >> sys.stderr, 'submit {0} for pgid {1}'.\
+                    format(json.dumps(cmddict), pgid)
+            ret, outbuf, outs = \
+                cluster_handle.pg_command(pgid, json.dumps(cmddict), inbuf,
+                                          timeout)
+
+        elif target[0] == 'mon':
+            if verbose:
+                print >> sys.stderr, '{0} to {1}'.\
+                    format(json.dumps(cmddict), target[0])
+            ret, outbuf, outs = cluster_handle.mon_command(json.dumps(cmddict),
+                                                           inbuf, timeout)
+
+    except Exception as e:
+        raise RuntimeError('"{0}": exception {1}'.format(prefix, e))
+
+    return ret, outbuf, outs
+
+def admin_socket(asok_path, cmd):
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    try:
+        sock.connect(asok_path)
+        sock.sendall(' '.join(cmd) + '\0')
+
+        len_str = sock.recv(4)
+        l, = struct.unpack(">I", len_str)
+        ret = ''
+
+        got = 0
+        while got < l:
+            bit = sock.recv(l - got)
+            ret += bit
+            got += len(bit)
+
+    except Exception as e:
+        raise RuntimeError('exception: {0}'.format(e))
+
+    return ret
+
+
+def ceph_conf(field, name):
+    p = subprocess.Popen(
+        args=[
+            'ceph-conf',
+            field,
+            '-n',
+            name,
+            ],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE)
+    outdata, errdata = p.communicate()
+    if (len(errdata)):
+        raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
+    return outdata.rstrip()
+
+def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
+    """
+    Do new-style command dance.
+    target: daemon to receive command: mon (any) or osd.N
+    sigdict - the parsed output from the new monitor describing commands
+    inbuf - any -i input file data
+    verbose - bool
+    """
+    if verbose:
+        for cmdtag in sorted(sigdict.keys()):
+            cmd = sigdict[cmdtag]
+            sig = cmd['sig']
+            print '{0}: {1}'.format(cmdtag, concise_sig(sig))
+
+    got_command = False
+
+    if not got_command:
+        if cmdargs:
+            # Validate input args against list of sigs
+            valid_dict = validate_command(parsed_args, sigdict, cmdargs)
+            if valid_dict:
+                got_command = True
+            else:
+                return -errno.EINVAL, '', 'invalid command'
+        else:
+            # do the command-interpreter looping
+            while True:
+                interactive_input = raw_input('ceph> ')
+                if interactive_input in ['q', 'quit', 'Q']:
+                    return 0, '', ''
+                cmdargs = parse_cmdargs(interactive_input.split())[2]
+                target = find_cmd_target(cmdargs)
+                valid_dict = validate_command(parsed_args, sigdict, cmdargs)
+                if valid_dict:
+                    if verbose:
+                        print >> sys.stderr, "Submitting command ", valid_dict
+                    ret, outbuf, outs = json_command(target=target,
+                                                     argdict=valid_dict)
+                    if ret:
+                        sys.stderr.write('Error {0}: {1}'.format(ret, outs))
+                        return ret, '', outs
+                else:
+                    print "invalid command"
+
+    if verbose:
+        print >> sys.stderr, "Submitting command ", valid_dict
+    return json_command(target=target, argdict=valid_dict, inbuf=inbuf)
+
+OSD_TELL_MATCH = { \
+    'sig': ['tell', {'name':'target','type':'CephName'}], \
+    'matchnum': 2, \
+    'return_key': 'target', \
+}
+PGID_MATCH = { \
+    'sig': ['pg', {'name':'pgid','type':'CephPgid'}], \
+    'matchnum': 2, \
+    'return_key': 'pgid', \
+}
+
+def find_cmd_target(childargs):
+    """
+    Using a minimal validation, figure out whether the command
+    should be sent to a monitor or an osd.  We do this before even
+    asking for the 'real' set of command signatures, so we can ask the
+    right daemon.
+    Returns ('osd', osdid), ('pg', pgid), or ('mon', '')
+    """
+    sig = parse_funcsig(['tell', {'name':'target','type':'CephName'}])
+    valid_dict = validate(childargs, sig, partial=True);
+    if len(valid_dict) == 2:
+        name = CephName()
+        name.valid(valid_dict['target'])
+        return 'osd', name.nameid
+
+    sig = parse_funcsig(['pg', {'name':'pgid','type':'CephPgid'}])
+    valid_dict = validate(childargs, sig, partial=True);
+    if len(valid_dict) == 2:
+        return 'pg', valid_dict['pgid']
+
+    return 'mon', ''
+
+def complete(sigdict, args, target):
+    """
+    Command completion.  Match as much of [args] as possible,
+    and print every possible match separated by newlines. 
+    Return exitcode.
+    """
+    # XXX this looks a lot like the front of validate_command().  Refactor?
+
+    complete_verbose = 'COMPVERBOSE' in os.environ
+
+    # Repulsive hack to handle tell: lop off 'tell' and target
+    # and validate the rest of the command.  'target' is already
+    # determined in our callers, so it's ok to remove it here.
+    if len(args) and args[0] == 'tell':
+        args = args[2:]
+    # look for best match, accumulate possibles in bestcmds
+    # (so we can maybe give a more-useful error message)
+    best_match_cnt = 0
+    bestcmds = []
+    for cmdtag, cmd in sigdict.iteritems():
+        sig = cmd['sig']
+        matched = matchnum(args, sig, partial=True)
+        if (matched > best_match_cnt):
+            if complete_verbose:
+                print >> sys.stderr, \
+                    "better match: {0} > {1}: {2}:{3} ".format(matched,
+                                  best_match_cnt, cmdtag, concise_sig(sig))
+            best_match_cnt = matched
+            bestcmds = [{cmdtag:cmd}]
+        elif matched == best_match_cnt:
+            if complete_verbose:
+                print >> sys.stderr, \
+                    "equal match: {0} > {1}: {2}:{3} ".format(matched,
+                                  best_match_cnt, cmdtag, concise_sig(sig))
+            bestcmds.append({cmdtag:cmd})
+
+    # look through all matching sigs
+    comps = []
+    for cmddict in bestcmds:
+        for cmd in cmddict.itervalues():
+            sig = cmd['sig']
+            # either:
+            #   we match everything fully, so we want the next desc, or
+            #   we match more partially, so we want the partial match
+            fullindex = matchnum(args, sig, partial=False) - 1
+            partindex = matchnum(args, sig, partial=True) - 1
+            if complete_verbose:
+                print >> sys.stderr, '{}: f {} p {} len {}'.format(sig, fullindex, partindex, len(sig))
+            if fullindex == partindex and fullindex + 1 < len(sig):
+                d = sig[fullindex + 1]
+            else:
+                d = sig[partindex]
+            comps.append(str(d))
+    if complete_verbose:
+        print >> sys.stderr, '\n'.join(comps)
+    print '\n'.join(comps)
+
+    return 0
+
+###
+# main
+###
+
+def main():
+
+    parser, parsed_args, childargs = parse_cmdargs()
+
+    if parsed_args.v:
+        print 'ceph version {0} ({1})'.format(CEPH_GIT_NICE_VER, CEPH_GIT_VER)
+       return 0
+
+    global verbose
+    verbose = parsed_args.verbose
+
+    # pass on --id, --name, --conf
+    name = 'client.admin'
+    if parsed_args.client_id:
+        name = 'client.' + parsed_args.client_id
+    if parsed_args.client_name:
+        name = parsed_args.client_name
+
+    # default '' means default conf search
+    conffile = ''
+    if parsed_args.cephconf:
+        conffile = parsed_args.cephconf
+    # For now, --admin-daemon is handled as usual.  Try it
+    # first in case we can't connect() to the cluster
+    if parsed_args.admin_socket:
+        try:
+            print admin_socket(parsed_args.admin_socket, childargs)
+        except Exception as e:
+            print >> sys.stderr, 'admin_socket: {0}'.format(e)
+        return 0
+
+    if len(childargs) > 0 and childargs[0] == "daemon":
+        if len(childargs) > 2:
+            if childargs[1].find('/') >= 0:
+                try:
+                    print admin_socket(childargs[1], childargs[2:])
+                except Exception as e:
+                    print >> sys.stderr, 'admin_socket: {0}'.format(e)
+                return 0
+            else:
+                # try resolve daemon name
+                path = ceph_conf('admin_socket', childargs[1])
+                try:
+                    print admin_socket(path, childargs[2:])
+                except Exception as e:
+                    print >> sys.stderr, 'admin_socket: {0}'.format(e)
+                return 0
+        else:
+            print >> sys.stderr, 'Daemon requires at least 2 arguments'
+            return 1
+
+    # handle any 'generic' ceph arguments that we didn't parse here
+    global cluster_handle
+
+    # rados.Rados() will call rados_create2, and then read the conf file,
+    # and then set the keys from the dict.  So we must do these
+    # "pre-file defaults" first (see common_preinit in librados)
+    conf_defaults = {
+        'log_to_stderr':'true',
+        'err_to_stderr':'true',
+        'log_flush_on_exit':'true',
+    }
+
+    clustername = 'ceph'
+    if parsed_args.cluster:
+        clustername = parsed_args.cluster
+
+    cluster_handle = rados.Rados(name=name, clustername=clustername,
+                                 conf_defaults=conf_defaults, conffile='')
+
+    retargs = cluster_handle.conf_parse_argv(childargs)
+    #tmp = childargs
+    childargs = retargs
+    if not childargs:
+        childargs = []
+
+    # -- means "stop parsing args", but we don't want to see it either
+    if '--' in childargs:
+        childargs.remove('--')
+
+    try:
+        cluster_handle.connect()
+    except KeyboardInterrupt:
+        print >> sys.stderr, 'Cluster connection aborted'
+        return 1
+    except Exception as e:
+        print >> sys.stderr, 'Error connecting to cluster: {0}'.\
+            format(e.__class__.__name__)
+        return 1
+
+    if parsed_args.help or parsed_args.help_all:
+        return do_help(parser, parsed_args.help_all)
+
+    # implement -w/--watch_*
+    # This is ugly, but Namespace() isn't quite rich enough.
+    level = ''
+    for k,v in parsed_args._get_kwargs():
+        if k.startswith('watch') and v:
+            if k == 'watch':
+                level = 'info'
+            else:
+                level = k.replace('watch_', '')
+    if level:
+
+        # an awfully simple callback
+        def watch_cb(arg, line, who, stamp_sec, stamp_nsec, seq, level, msg):
+            print line
+
+        # first do a ceph status
+        ret, outbuf, outs = json_command(prefix='status')
+        if ret:
+            print >> sys.stderr, "status query failed: ", outs
+            return ret
+        print outbuf
+
+        # this instance keeps the watch connection alive, but is
+        # otherwise unused
+        logwatch = rados.MonitorLog(cluster_handle, level, watch_cb, 0)
+        # loop forever letting watch_cb print lines
+        while True:
+            try:
+                pass
+            except KeyboardInterrupt:
+                # or until ^C, at least
+                return 0
+
+    # read input file, if any
+    inbuf = ''
+    if parsed_args.input_file:
+        try:
+            with open(parsed_args.input_file, 'r') as f:
+                inbuf = f.read()
+        except Exception as e:
+            print >> sys.stderr, 'Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e)
+            return 1
+
+    # prepare output file, if any
+    if parsed_args.output_file:
+        try:
+            outf = open(parsed_args.output_file, 'w')
+        except:
+            print >> sys.stderr, \
+                'Can\'t open output file {0}: {1}'.\
+                format(parsed_args.output_file, e)
+            return 1
+
+    # -s behaves like a command (ceph status).
+    if parsed_args.status:
+        childargs.insert(0, 'status')
+
+    target = find_cmd_target(childargs)
+
+    # fetch JSON sigs from command
+    # each line contains one command signature (a placeholder name
+    # of the form 'cmdNNN' followed by an array of argument descriptors)
+    # as part of the validated argument JSON object
+
+    ret, outbuf, outs = json_command(target=target,
+                                     prefix='get_command_descriptions')
+    if ret == -errno.EINVAL:
+        # send command to old monitor
+        if verbose:
+            print '{0} to old monitor'.format(' '.join(childargs))
+        ret, outbuf, outs = cluster_handle.mon_command(childargs, inbuf)
+    elif ret:
+        if ret < 0:
+            ret = -ret
+        print >> sys.stderr, \
+            'Problem getting command descriptions from {0}, {1}'.\
+            format(target, errno.errorcode[ret])
+        return ret
+    else:
+        sigdict = parse_json_funcsigs(outbuf)
+
+        if parsed_args.completion:
+            return complete(sigdict, childargs, target)
+
+        ret, outbuf, outs = new_style_command(parsed_args, childargs, target,
+                                              sigdict, inbuf, verbose)
+
+    if ret < 0:
+        ret = -ret
+        print >> sys.stderr, 'Error {0}: {1}'.format(errno.errorcode[ret], outs)
+        return ret
+
+    # this assumes outs never has useful command output, only status
+    if outs:
+        print >> sys.stderr, outs
+
+    if (parsed_args.output_file):
+        outf.write(outbuf)
+        outf.close()
+    else:
+        # hack: old code printed status line before many json outputs
+        # (osd dump, etc.) that consumers know to ignore.  Add blank line
+        # to satisfy consumers that skip the first line, but not annoy
+        # consumers that don't.
+        if parsed_args.output_format and \
+           parsed_args.output_format.startswith('json'):
+            sys.stdout.write('\n');
+
+        sys.stdout.write(outbuf)
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())