File: //lib64/python2.7/site-packages/block/device.py
#!/usr/bin/python
#
# Copyright 2005-2007 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import sys as _sys
import os as _os
import stat as _stat
import string as _string
def DeviceMaps():
import dm as _dm
mlist = _dm.maps()
del _dm
devs = {}
for m in mlist:
devs[m.dev] = m
maps = {}
for d,m in devs.items():
mdeps = filter(lambda x: devs.has_key(x), m.deps)
for mdep in mdeps:
key = devs[mdep]
maps.setdefault(key, []).append(m)
return maps
def removeDeviceMap(map):
maps = DeviceMaps()
try:
for m in maps[map]:
removeDeviceMap(m)
except KeyError:
pass
map.remove()
def map_dev(path):
if path[0] != '/':
return path
try:
statinfo = _os.stat(path)
if not _stat.S_ISBLK(statinfo.st_mode):
return path
return "%d:%d" % (statinfo.st_rdev/256, statinfo.st_rdev%256, )
except:
return path
# Helper function for get_map
# The tables will be considered the same if for every row, with everything else
# being the same, they contain the same sets of devices.
def compare_tables(t1, t2):
for table1, table2 in zip(t1, t2):
table1 = str(table1).strip().split(' ')
table2 = str(table2).strip().split(' ')
table1sets = []
table2sets = []
# We do this to avoid the index out of range exception
if len(table1) != len(table2):
return False
for i in range(len(table1)):
d1 = map_dev(table1[i])
d2 = map_dev(table2[i])
# when at least one changes its a device.
if (table1[i] != d1) or (table2[i] != d2):
# The d{1,2} will always have the major:minor string
# We also need what comes after the dev, the offset.
try:
table1sets.append("%s %s" % (d1, table1[i+1]))
table2sets.append("%s %s" % (d2, table2[i+1]))
i += 1
except IndexError, msg:
# The device must have an offset, if not its nonesense
return False
continue
# these are not devices
if d1 == d2:
continue
if d1 != d2:
return False
# For mirror sets the devices can be in disorder.
if table1[2] == "mirror":
if set(table1sets) != set(table2sets):
return False
# For none mirror the devs have to be in order.
else:
for i in range(len(table1sets)):
if table1sets[i] != table2sets[i]:
return False
return True
class BlockDev:
def get_major(self):
return self._BlockDev__device.major
def set_major(self, major):
self._BlockDev__device.major = major
major = property(get_major, set_major, None, "major device number")
def get_minor(self):
return self.__device.minor
def set_minor(self, minor):
self._BlockDev__device.minor = minor
minor = property(get_minor, set_minor, None, "minor device number")
def get_device(self):
return self._BlockDev__device
device = property(get_device, None, None, "device number")
def get_dmdev(self):
return "%s:%s" % (self._BlockDev__device.major, self._BlockDev__device.minor)
dmdev = property(get_dmdev, None, None, "device number formatted for dm")
def get_path(self):
return self._BlockDev__path
def set_path(self, path):
self._BlockDev__path = str(path)
path = property(get_path, set_path, None, "path to device node")
def get_mode(self):
return self._BlockDev__device.mode
def set_mode(self, mode):
self._BlockDev__device.mode = int(mode)
mode = property(get_mode, set_mode, None, "mode for device node")
def __init__(self, path=None, major=None, minor=None, dev=None):
self._BlockDev__context = None
self._BlockDev__path = None
if not path is None:
self.FromFile(path)
elif not (major is None and minor is None):
self.FromMajorMinor(major, minor)
elif not dev is None:
self.FromDev(dev)
else:
import dm as _dm
self._BlockDev__device = _dm.device(0,0)
del _dm
# self.sysfs = None # XXX not handling yet
# XXX should probe parents/children/etc
self.parents = []
self.children = []
self.group = None
self.group_position = None
self.group_siblings = []
def FromFile(self, path):
import dm as _dm
path = str(path)
self._BlockDev__path = path
self._BlockDev__device = _dm.device(path=path)
del _dm
return self;
def FromMajorMinor(self, major, minor):
import dm as _dm
self._BlockDev__device = _dm.device(major, minor)
del _dm
return self
def FromDev(self, dev):
import dm as _dm
self._BlockDev__device == _dm.device(0,0)
self._BlockDev__device.dev = dev
del _dm
return self
def create(self, path=None, mode=None, context=None):
args = {}
if path is None:
path = self.path
if path is None:
raise ValueError, "no path set for %s" % (self,)
args['path'] = path
args['mode'] = self.mode
if not mode is None:
args['mode'] = mode
if context is None:
context = self._BlockDev__context
if not context is None:
args['context'] = context
return apply(self.device.mknod, (), args)
def output(self, *args):
for x in args:
print x
def remove(self):
if not self.path:
raise RuntimeError, "no path set for %s" % (self,)
if self.path.split('/')[-1].startswith('VolGroup'):
raise RuntimeError, "trying to unlink %s" % (self.path,)
_os.unlink(self.path)
class Device:
def get_major(self):
return self.__bdev.major
def set_major(self, major):
self.__bdev.major = major
major = property(get_major, set_major, None, "major device number")
def get_minor(self):
return self.__bdev.minor
def set_minor(self, major):
self.__bdev.minor = major
minor = property(get_major, set_major, None, "major device number")
def get_dmdev(self):
return self.__bdev.dmdev
dmdev = property(get_dmdev, None, None, "device number formatted for dm")
def get_map(self):
return self.__map
def set_map(self, map):
# XXX type chek this
self.__map = map
map = property(get_map, set_map, None, "device map for this device")
def __init__(self, bdev=BlockDev(), map=None):
if not isinstance(bdev, BlockDev):
raise TypeError, "bdev must be an instance of block.BlockDev"
self.__bdev = bdev
self.__map = map
def FromFile(self, path):
self.__bdev = BlockDev().FromFile(path)
self.__map = None
from UserDict import IterableUserDict as _IUD
class MPNameCache(_IUD):
import dm as _dm
# we'll get other maps here which will never be dereffed, but we
# also won't wind up using them for .new(), so that's good.
data = {}
for map in _dm.maps():
data.setdefault(map.name, 1)
del _dm
def __init__(self):
_IUD.__init__(self)
self.data = MPNameCache.data
def new(self):
n = 0
while True:
name = 'mpath%s' % (n,)
if self.try_get(name):
return name
n += 1
def get(self, name):
self.setdefault(name, 0)
self[name] += 1
return name
def try_get(self, name):
if not self.has_key(name):
self.get(name)
return True
return False
def put(self, name):
self[name] -= 1
if self[name] == 0:
del self[name]
def rename(self, old_name, new_name):
self[new_name] = self[old_name]
del self[old_name]
return new_name
nameCache = MPNameCache()
del _IUD
class MultiPath:
def __init__(self, *bdevs):
self._MultiPath__prefix = "/dev/mapper/"
self._MultiPath__bdev = None
self._MultiPath__table = None
self._MultiPath__map = None
self._MultiPath__parts = []
# this sets the refcount to 1 (undone by __del__)
self._MultiPath__name = nameCache.new()
self.active = False
self.mknod = False
self.bdevs = []
if bdevs:
self.checkBdevs(bdevs)
table = self.get_table()
import dm as _dm
self.get_map()
# this sets the refcount to 2 (undone by __deactivate__)
nameCache.get(self.name)
def __del__(self):
if not nameCache[self.name] in [1, 2]:
raise RuntimeError, "%s has refcount %s on __del__" % (self.name,
nameCache[self.name])
nameCache.put(self.name)
def firstDevPath(self):
l = list(self.member_devpaths)
l.sort()
return l[0]
def __cmp__(self, other):
s = self.firstDevPath()
o = other.firstDevPath()
if s < o:
return -1
elif s == o:
return cmp(self.map, other.map)
return 1
def checkBdevs(self, bdevs):
bdh = {}
for x in self.bdevs:
bdh[x] = None
for bdev in bdevs:
if not bdh.has_key(bdev):
self.bdevs.append(bdev)
import os as _os
self.size = None
rdevs = {}
for bdev in self.bdevs:
sb = _os.stat(bdev)
rdev = sb.st_rdev
if rdevs.has_key(rdev):
continue
rdevs[rdev] = 1
sysfile = '/sys/block/%s/size' % (bdev.split('/')[-1],)
f = open(sysfile)
size = f.readlines()[0]
del f
size = int(size.strip())
if self.size is None:
self.size = size
if self.size != size:
raise ValueError, "mismatched sizes"
del _os
def get_prefix(self):
return self._MultiPath__prefix
def set_prefix(self, value):
self._MultiPath__prefix = value
prefix = property(get_prefix, set_prefix, None, \
"prefix for path to device nodes")
def _set_map_uuid(self, map):
if map and not map.uuid:
map.uuid = "MPATH-%s" % map.name
def get_PedDevice(self):
import parted as _parted
ret = _parted.getDevice(self.bdev.path)
del _parted
return ret
PedDevice = property(get_PedDevice, None, None, "parted.PedDevice")
def get_members(self, descend=True):
for bdev in self.bdevs:
yield bdev
members = property(get_members, None, None, "members")
def get_member_devpaths(self, descend=True):
for m in self.get_members(descend):
if str(m).startswith("/dev/"):
yield str(m)
else:
yield "/dev/" + m
member_devpaths = property(get_member_devpaths, None, None, "member devpaths")
def get_bdev(self):
if not self._MultiPath__bdev is None:
return self._MultiPath__bdev
try:
self._MultiPath__bdev = BlockDev(self.prefix + self.name)
return self._MultiPath__bdev
except:
raise
pass
bdev = property(get_bdev, None, None, "block.BlockDev")
def get_table(self):
if not self._MultiPath__table is None:
return self._MultiPath__table
# we get "/dev/hda" from one and "3:0" from the other, so we have to
# fix up the device name
def munge_dev(path):
if path[0] != '/':
return path.strip()
bd = map_dev(path)
# starting with 2.6.17-1.2510.fc6 or so, there's an implicit
# minimum IOs of 1000, which gets _added_ to the line "dmsetup ls"
# shows. This sucks.
return "%s 1000" % (bd,)
tableParts = [0, self.size, 'multipath']
params = '0 0 1 1 round-robin 0 %s 1 %s' % (len(self.bdevs), \
_string.join(map(munge_dev, self.bdevs)))
tableParts.append(params)
import dm as _dm
table = apply(_dm.table, tableParts, {})
del _dm
self._MultiPath__table = [ table ]
return self._MultiPath__table
table = property(get_table, None, None, "block.dm.table")
def get_map(self):
if not self._MultiPath__map is None:
return self._MultiPath__map
table = self.get_table()
import dm as _dm
for map in _dm.maps():
if compare_tables(map.table, table):
if self.name != map.name:
self.name = nameCache.rename(self.name, map.name)
self._MultiPath__map = map
self.buildParts()
self.active = True
del _dm
return self._MultiPath__map
# all else has failed, make a new map...
self._MultiPath__map = _dm.map(name=self.name, table=table)
self.buildParts()
self.active = True
del _dm
return self._MultiPath__map
map = property(get_map, None, None, "block.dm.map")
def get_name(self):
return self._MultiPath__name
def set_name(self, name):
bdev = self._MultiPath__bdev
prefix = self.prefix
self.deactivate()
self._MultiPath__name = nameCache.rename(self._MultiPath__name, name)
self._MultiPath__bdev = BlockDev().FromMajorMinor(bdev.major, bdev.minor)
self.bdev.mode = 0600
self.bdev.path = prefix + name
self.activate()
name = property(get_name, set_name, None, "the name of this MultiPath")
def get_parts(self):
if self._MultiPath__parts is None:
self.buildParts()
for x in self._MultiPath__parts:
yield x
partitions = property(get_parts, None, None, "this device's partitions")
def output(self, *args):
for x in args:
print x
def buildPartMaps(self):
from maps import PartitionDeviceMap as _PartitionDeviceMap
import parted as _parted
import _ped
dev = self.PedDevice
dev.open()
try:
disk = _parted.Disk(dev)
except (_ped.DiskLabelException,_parted.DiskException), msg:
dev.close()
del dev
return
for part in disk.partitions:
if part.active:
name = "%sp%s" % (self.name, part.number)
bdev = BlockDev(self.prefix + self.name)
bdev.mode = 0600
if part.type != _parted.PARTITION_EXTENDED:
map = _PartitionDeviceMap(0, part.geometry.length, bdev,
part.geometry.start, part.number)
else:
# special mapping for extended partitions see the comment
# in RaidSet.buildPartMaps()
map = _PartitionDeviceMap(0, 2, bdev,
part.geometry.start, part.number)
map.name = name
self._set_map_uuid(map)
yield map
del disk
dev.close()
del dev
del _PartitionDeviceMap
del _parted
del _ped
def buildParts(self):
import dm as _dm
maps = self.buildPartMaps()
for map in maps:
map.create()
self._MultiPath__parts.append(map)
def removeMemberParts(self):
import dm as _dm
for x in range(1,257):
for m in self.member_devpaths:
_dm.rmpart(m, x)
del _dm
def scanMemberParts(self):
import dm as _dm
for m in self.member_devpaths:
_dm.scanparts(m)
del _dm
def activate(self, mknod=False):
if self.active:
return
if mknod:
try:
_os.unlink(self.prefix+self.name)
except:
pass
self.map.dev.mknod(self.prefix+self.name)
self.mknod = True
self._set_map_uuid(self.map)
nameCache.get(self.name) # this should set the refcount to 2
self.removeMemberParts()
self.buildParts()
ret = self.partitions
return ret
def deactivate(self):
if not self.active:
return
parts = self._MultiPath__parts
self._MultiPath__parts = []
for part in parts:
try:
part.map.remove()
except:
pass
parts = list(self.partitions)
if len(parts) == 0 and not self._MultiPath__map is None:
removeDeviceMap(self._MultiPath__map)
self._MultiPath__map = None
self.active = False
nameCache.put(self.name) # this should take the refcount to 1
elif len(parts) > 0:
raise RuntimeError, "multipath has active partitions"
self.scanMemberParts()
def display(self, space=0, printer=lambda x,y: _sys.stdout.write("%s%s\n" % (x*' ',y))):
printer(space, self)
for m in self.members:
m.display(space+1, printer=printer)
class RaidSet:
def __init__(self, rs, prefix="/dev/mapper/"):
self.rs = rs
self._RaidSet__prefix = prefix
self._RaidSet__bdev = None
self._RaidSet__map = None
self._RaidSet__parts = []
self._RaidSet__name = rs.name
self._RaidSet__activeMembers = []
self.active = False
self.mknod = False
def __cmp__(self, other):
return cmp(self.map, other.map)
def get_prefix(self):
return self._RaidSet__prefix
def set_prefix(self, value):
self._RaidSet__prefix = value
prefix = property(get_prefix, set_prefix, None, \
"prefix for path to device nodes")
def _set_map_uuid(self, map):
if map and not map.uuid:
# make sure the map has a uuid complete with subsystem prefix
map.uuid = "DMRAID-%s" % map.name
def get_PedDevice(self):
import parted as _parted
ret = _parted.getDevice(self.bdev.path)
del _parted
return ret
PedDevice = property(get_PedDevice, None, None, "parted.PedDevice")
def get_level(self):
# We do not handle layered raid properly here, nor raid5 nor JBOD
raise NotImplementedError, "FIXME"
if self.rs.dmtype in ("stripe", "striped"):
return 0
elif self.rs.dmtype == "mirror":
return 1
raise NotImplementedError, "unknown dmtype %s" % (self.rs.dmtype,)
level = property(get_level, None, None, "raid level")
def get_members(self, descend=True):
import dmraid as _dmraid
for c in self.rs.children:
if isinstance(c, _dmraid.raidset):
r = RaidSet(c, prefix=self.prefix)
yield r
if descend:
for m in r.members:
yield m
elif isinstance(c, _dmraid.raiddev):
yield RaidDev(c)
del _dmraid
members = property(get_members, None, None, "members")
def get_member_devpaths(self, descend=True):
for m in self.get_members(descend):
if isinstance(m, RaidDev):
yield m.devpath
member_devpaths = property(get_member_devpaths, None, None, "member devpaths")
def get_spares(self, descend=True):
import dmraid as _dmraid
for s in self.rs.spares:
if isinstance(c, _dmraid.raidset):
r = RaidSet(c)
yield r
if descend:
for m in r.members:
yield m
for m in r.spares:
yield m
elif isinstance(c, _dmraid.raiddev):
yield RaidDev(c)
# members might have spares as well
for c in self.rs.children:
if isinstance(c, _dmraid.raidset) and descend:
for spare in RaidSet(c).spares:
yield spare
del _dmraid
spares = property(get_spares, None, None, "spares")
def get_bdev(self):
if not self._RaidSet__bdev is None:
return self._RaidSet__bdev
try:
self._RaidSet__bdev = BlockDev(self.prefix + self.name)
return self._RaidSet__bdev
except:
pass
bdev = property(get_bdev, None, None, "block.BlockDev")
def get_map(self):
if not self._RaidSet__map is None:
return self._RaidSet__map
import dm as _dm
for map in _dm.maps():
if compare_tables(map.table, self.rs.dmTable):
self._RaidSet__map = map
self.active = True
del _dm
return self._RaidSet__map
# all else has failed, make a new map...
self._RaidSet__map = _dm.map(name=self.name, table=self.rs.dmTable)
self.active = True
del _dm
return self._RaidSet__map
map = property(get_map, None, None, "block.dm.map")
def get_name(self):
return self._RaidSet__name
def set_name(self, name):
bdev = self._RaidSet__bdev
prefix = self.prefix
self.deactivate()
self.rs.name = name
self._RaidSet__name = name
self._RaidSet__bdev = BlockDev().FromMajorMinor(bdev.major, bdev.minor)
self.bdev.mode = 0600
self.bdev.path = prefix + name
self.activate()
name = property(get_name, set_name, None, "this name of this RaidSet")
def get_parts(self):
for x in self._RaidSet__parts:
yield x
partitions = property(get_parts, None, None, "this device's partitions")
def get_valid(self):
if self.rs.broken:
return False
if self.rs.degraded:
return False
for x in self.members:
if isinstance(x, RaidDev) or isinstance(x, RaidSet):
if not x.valid:
return False
return True
valid = property(get_valid, None, None, "test a raidset for validity")
def output(self, *args):
for x in args:
print x
def buildParts(self):
from maps import PartitionDeviceMap as _PartitionDeviceMap
import parted as _parted
import _ped
dev = self.PedDevice
dev.open()
try:
disk = _parted.Disk(device=dev)
except (_ped.DiskLabelException,
_parted.DiskException,_parted.IOException), msg:
dev.close()
del dev
return
for part in disk.partitions:
if part.active:
name = "%sp%s" % (self.name, part.number)
bdev = BlockDev(self.prefix + self.name)
bdev.mode = 0600
if part.type != _parted.PARTITION_EXTENDED:
map = _PartitionDeviceMap(0, part.geometry.length, bdev,
part.geometry.start, part.number)
else:
# Various tools create different mappings for ext. parts:
# dmraid: Does not create a mapping for extended parts
# kpartx: Creates a mapping with a size of 2 sectors
# parted: Creates a mapping with the actual partition size
#
# The kernel does the same as kpartx for regular disks.
# We do as kpartx and create a 2 sector mapping, so that if
# there is a pre-existing mapping (ie a livecd install), we
# recognize it and don't try to create one.
map = _PartitionDeviceMap(0, 2, bdev,
part.geometry.start, part.number)
map.name = name
self._set_map_uuid(map)
map.create()
self._RaidSet__parts.append(map)
del disk
dev.close()
del dev
del _PartitionDeviceMap
del _parted
del _ped
def removeMemberParts(self):
for m in self.members:
if isinstance(m, RaidDev):
m.removeParts()
elif isinstance(m, RaidSet):
m.removeMemberParts()
def scanMemberParts(self):
for m in self.members:
if isinstance(m, RaidDev):
m.scanParts()
elif isinstance(m, RaidSet):
m.scanMemberParts()
def activate(self, degradedOk=False, mknod=False, mkparts=True):
if self.active:
return
# We put the active members in a list so we can deactivate them later.
for member in self.members:
if isinstance(member, RaidSet):
member.activate(degradedOk=degradedOk, mknod=mknod, mkparts=False)
self._RaidSet__activeMembers.append(member)
if mknod:
try:
_os.unlink(self.prefix+self.name)
except:
pass
self.map.dev.mknod(self.prefix+self.name)
self.mknod = True
self._set_map_uuid(self.map)
self.removeMemberParts()
if mkparts:
self.buildParts()
ret = self.partitions
return ret
def deactivate(self):
if not self.active:
return
parts = self._RaidSet__parts
self._RaidSet__parts = []
for part in parts:
try:
part.map.remove()
except:
pass
parts = list(self.partitions)
if len(parts) == 0 and not self._RaidSet__map is None:
removeDeviceMap(self._RaidSet__map)
self._RaidSet__map = None
self.active = False
elif len(parts) > 0:
raise RuntimeError, "raidset has active partitions"
for activeMember in self._RaidSet__activeMembers:
activeMember.deactivate()
self._RaidSet__activeMembers = []
self.scanMemberParts()
def display(self, space=0, printer=lambda x,y: _sys.stdout.write("%s%s\n" % (x*' ',y))):
printer(space, self)
for m in self.members:
m.display(space+1, printer=printer)
class RaidDev:
def __init__(self, rd, prefix=None):
self.rd = rd
self._RaidDev__bdev = None
self._RaidDev__prefix = None
def get_prefix(self):
if self._RaidDev__prefix is None:
self._RaidDev__prefix = \
_string.join(self.rd.device.path.split('/')[:-1],'/') + '/'
return self._RaidDev__prefix
prefix = property(get_prefix, None, None, "prefix for device node path")
def get_bdev(self):
if self._RaidDev__bdev is None:
bdev = BlockDev().FromFile(self.rd.device.path)
self._RaidDev__bdev = bdev
return self._RaidDev__bdev
bdev = property(get_bdev, None, None, "block.BlockDev")
def get_PedDevice(self):
import parted as _parted
ret = _parted.getDevice(self.bdev.path)
del _parted
return ret
PedDevice = property(get_PedDevice, None, None, "parted.PedDevice")
def get_devpath(self):
return self.rd.device.path.split('/')[-1]
devpath = property(get_devpath, None, None, "path relative to /dev")
def get_valid(self):
if self.bdev is not None and \
hasattr(self.bdev, 'path') and self.bdev.path is not None:
return True
return False
valid = property(get_valid, None, None, "test a raiddev for validity")
def __cmp__(self, other):
# Dear Python, you are retarded.
def strcmp(a,b):
ab = zip(reduce(lambda x,y: x + [ord(y)], list(a), []),
reduce(lambda x,y: x + [ord(y)], list(b), []))
cmps = filter(None, map(lambda (x,y): cmp(x,y), ab)) or \
[len(a) - len(b),]
return cmps[0]
obdev = other
if isinstance(other, "".__class__):
filename = '/dev/' + other
if other[0].isdigit():
maj,min = (0,0)
if ':' in other:
maj,min = other.split(':')[0:2]
obdev = BlockDev().FromMajorMinor(maj,min)
else:
device = str(self.bdev.device)
return strcmp(device, other)
elif other.startswith("/"):
try:
obdev = BlockDev().FromFile(other)
except:
filename = self.devpath
elif other.startswith('/dev/mapper/'):
filename = '/dev/mapper/' + self.devpath
elif other.startswith('/dev/'):
filename = '/dev/' + self.devpath
elif other.startswith('/tmp/'):
filename = '/tmp/' + self.devpath
try:
obdev = BlockDev().FromFile(filename)
except:
return strcmp(filename, other)
if isinstance(obdev, BlockDev):
try:
return cmp(self.bdev.device.dev, obdev.device.dev)
except:
pass
elif isinstance(other, RaidDev):
if self.rd.device.serial is not None and \
other.rd.device.serial is not None:
return strcmp(self.rd.device.serial, other.rd.device.serial)
elif self.bdev is not None and other.bdev is not None:
return strcmp(self.bdev.device.dev, other.bdev.device.dev)
else:
return strcmp(self.devpath, other.devpath)
return -1
def removeParts(self):
for x in range(1,257):
self.rd.device.rmpart(x)
def scanParts(self):
self.rd.device.scanparts()
def display(self, space=0, printer=lambda x,y: _sys.stdout.write("%s%s\n" % (x*' ',y))):
printer(space, self)
#
# vim:ts=8:sts=4:sw=8:et
#