From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) by finch.gentoo.org (Postfix) with ESMTP id ADB051381F3 for ; Tue, 22 Oct 2013 16:09:39 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 43F55E0B80; Tue, 22 Oct 2013 16:09:37 +0000 (UTC) Received: from mail-ea0-f182.google.com (mail-ea0-f182.google.com [209.85.215.182]) (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits)) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 6871DE0B7E for ; Tue, 22 Oct 2013 16:09:36 +0000 (UTC) Received: by mail-ea0-f182.google.com with SMTP id o10so4302653eaj.41 for ; Tue, 22 Oct 2013 09:09:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:user-agent:references:in-reply-to:mime-version :content-type:content-transfer-encoding:message-id; bh=1gPgtWFPanRkuqJ5+XhPbmbCEwQHXuL6OwwILwrVSas=; b=OgN4Y36bJB5IwJSxxaY/dwUC9kvx2nGSjnVdd7BxgxHzaOv2juTKNq4FxSH+WodBhx sap9pLZ96gdLO0Ow+YEFuFFtxSf2yVEfET98lNy0CxljVKrBa52xavYLwpCkyCeIE0ro VkK1NYl59mBNhyymro9llEcpKPBz5zXO9h5yLukBt7LlZ0YzKnU9itBdQJ1l9r7eH/nl VtBFoLKCmwp9hxrumYiQMSCsQjncvUgHvFVLg1qcpRKdffo17Ns9B7YuKJGMXsMG7fVS gawJdw7iHU+Het+Yxd6O6jT7kreZx3+RsKQaM/BKLdOSgbSOlg8HuN8Z7jxspX8h1ebs 3/Ww== X-Received: by 10.14.177.199 with SMTP id d47mr29502238eem.14.1382458174948; Tue, 22 Oct 2013 09:09:34 -0700 (PDT) Received: from afta-picea.localnet (202-141-251-94.net.stream.pl. [94.251.141.202]) by mx.google.com with ESMTPSA id j7sm58438962eeo.15.2013.10.22.09.09.33 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Tue, 22 Oct 2013 09:09:34 -0700 (PDT) From: Arfrever Frehtes Taifersar Arahesis To: Gentoo Portage Development Subject: Re: [gentoo-portage-dev] [PATCH v2] xattr: centralize the various shims in one place Date: Tue, 22 Oct 2013 18:09:02 +0200 User-Agent: KMail (GNU/Linux) References: <1381957406-21749-1-git-send-email-vapier@gentoo.org> <1382324828-25755-1-git-send-email-vapier@gentoo.org> In-Reply-To: <1382324828-25755-1-git-send-email-vapier@gentoo.org> Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-portage-dev@lists.gentoo.org Reply-to: gentoo-portage-dev@lists.gentoo.org MIME-Version: 1.0 Content-Type: multipart/signed; boundary="nextPart2343426.czUQRC1VY1"; protocol="application/pgp-signature"; micalg=pgp-sha512 Content-Transfer-Encoding: 7bit Message-Id: <201310221809.02426.Arfrever.FTA@gmail.com> X-Archives-Salt: 39e657a7-716c-492d-9f71-f0ff4298ae7e X-Archives-Hash: d2d64f7aaf2af5bada28c2172787b503 --nextPart2343426.czUQRC1VY1 Content-Type: Text/Plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 2013-10-21 05:07 Mike Frysinger napisa=C5=82(a): > Rather than each module implementing its own shim around the various > methods for accessing extended attributes, start a dedicated module > that exports a consistent API. > --- > v2 > - passes unittests w/python 2.6 2.7 3.2 3.3 But portage.util._xattr._XattrSystemCommands does not work :) . >>> import portage.util._xattr >>> portage.util._xattr._XattrSystemCommands.list("/tmp") =2E.. See below. > bin/xattr-helper.py | 11 +- > pym/portage/tests/util/test_xattr.py | 178 ++++++++++++++++++++++++++++++ > pym/portage/util/_xattr.py | 205 +++++++++++++++++++++++++++++= ++++++ > pym/portage/util/movefile.py | 100 ++++------------- > 4 files changed, 407 insertions(+), 87 deletions(-) > create mode 100644 pym/portage/tests/util/test_xattr.py > create mode 100644 pym/portage/util/_xattr.py >=20 > diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py > index 6d99521..69b83f7 100755 > --- a/bin/xattr-helper.py > +++ b/bin/xattr-helper.py > @@ -17,16 +17,7 @@ import re > import sys > =20 > from portage.util._argparse import ArgumentParser > - > -if hasattr(os, "getxattr"): > - > - class xattr(object): > - get =3D os.getxattr > - set =3D os.setxattr > - list =3D os.listxattr > - > -else: > - import xattr > +from portage.util._xattr import xattr > =20 > =20 > _UNQUOTE_RE =3D re.compile(br'\\[0-7]{3}') > diff --git a/pym/portage/tests/util/test_xattr.py b/pym/portage/tests/uti= l/test_xattr.py > new file mode 100644 > index 0000000..e1f6ee8 > --- /dev/null > +++ b/pym/portage/tests/util/test_xattr.py > @@ -0,0 +1,178 @@ > +# Copyright 2010-2013 Gentoo Foundation > +# Distributed under the terms of the GNU General Public License v2 > + > +"""Tests for the portage.util._xattr module""" > + > +from __future__ import print_function > + > +try: > + # Try python-3.3 module first. > + from unittest import mock > +except ImportError: > + try: > + # Try standalone module. > + import mock > + except ImportError: > + mock =3D None > + > +import subprocess > + > +from portage.tests import TestCase > +from portage.util._xattr import (xattr as _xattr, _XattrSystemCommands, > + _XattrStub) > + > + > +orig_popen =3D subprocess.Popen > +def MockSubprocessPopen(stdin): > + """Helper to mock (closely) a subprocess.Popen call > + > + The module has minor tweaks in behavior when it comes to encoding and > + python versions, so use a real subprocess.Popen call to fake out the > + runtime behavior. This way we don't have to also implement different > + encodings as that gets ugly real fast. > + """ > + proc =3D orig_popen(['cat'], stdout=3Dsubprocess.PIPE, stdin=3Dsubproce= ss.PIPE) > + try: > + proc.stdin.write(bytes(stdin)) > + except TypeError: > + proc.stdin.write(bytes(stdin, 'ascii')) It can fail with UnicodeEncodeError. Use proc.stdin.write(portage._unicode_encode(stdin), portage._encodings['st= dio']) instead of above 4 lines. > + return proc > + > + > +class SystemCommandsTest(TestCase): > + """Test _XattrSystemCommands""" > + > + OUTPUT =3D '\n'.join([ > + '# file: /bin/ping', > + 'security.capability=3D0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=3D', > + 'user.foo=3D"asdf"', > + '', > + ]) > + > + def _setUp(self): > + if mock is None: > + self.skipTest('need mock for testing') > + > + return _XattrSystemCommands > + > + def _testGetBasic(self): > + """Verify the get() behavior""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify basic behavior, and namespace arg works as expected. > + xattr.get('/some/file', 'user.foo') > + xattr.get('/some/file', 'foo', namespace=3D'user') > + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_lis= t[1]) > + > + # Verify nofollow behavior. > + call_mock.reset() > + xattr.get('/some/file', 'user.foo', nofollow=3DTrue) > + self.assertIn('-h', call_mock.call_args[0][0]) > + > + def testGetParsing(self): > + """Verify get() parses output sanely""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify output parsing. > + call_mock.return_value =3D MockSubprocessPopen('\n'.join([ > + '# file: /some/file', > + 'user.foo=3D"asdf"', > + '', > + ])) > + call_mock.reset() > + self.assertEqual(xattr.get('/some/file', 'user.foo'), b'"asdf"') > + > + def testGetAllBasic(self): > + """Verify the get_all() behavior""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify basic behavior. > + xattr.get_all('/some/file') > + > + # Verify nofollow behavior. > + call_mock.reset() > + xattr.get_all('/some/file', nofollow=3DTrue) > + self.assertIn('-h', call_mock.call_args[0][0]) > + > + def testGetAllParsing(self): > + """Verify get_all() parses output sanely""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify output parsing. > + call_mock.return_value =3D MockSubprocessPopen(self.OUTPUT) > + exp =3D [ > + (b'security.capability', b'0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=3D'), > + (b'user.foo', b'"asdf"'), > + ] > + self.assertEqual(exp, xattr.get_all('/some/file')) > + > + def testSetBasic(self): > + """Verify the set() behavior""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify basic behavior, and namespace arg works as expected. > + xattr.set('/some/file', 'user.foo', 'bar') > + xattr.set('/some/file', 'foo', 'bar', namespace=3D'user') > + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_lis= t[1]) > + > + def testListBasic(self): > + """Verify the list() behavior""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify basic behavior. > + xattr.list('/some/file') > + > + # Verify nofollow behavior. > + call_mock.reset() > + xattr.list('/some/file', nofollow=3DTrue) > + self.assertIn('-h', call_mock.call_args[0][0]) > + > + def testListParsing(self): > + """Verify list() parses output sanely""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify output parsing. > + call_mock.return_value =3D MockSubprocessPopen(self.OUTPUT) > + exp =3D [b'security.capability', b'user.foo'] > + self.assertEqual(exp, xattr.list('/some/file')) > + > + def testRemoveBasic(self): > + """Verify the remove() behavior""" > + xattr =3D self._setUp() > + with mock.patch.object(subprocess, 'Popen') as call_mock: > + # Verify basic behavior, and namespace arg works as expected. > + xattr.remove('/some/file', 'user.foo') > + xattr.remove('/some/file', 'foo', namespace=3D'user') > + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_lis= t[1]) > + > + # Verify nofollow behavior. > + call_mock.reset() > + xattr.remove('/some/file', 'user.foo', nofollow=3DTrue) > + self.assertIn('-h', call_mock.call_args[0][0]) > + > + > +class StubTest(TestCase): > + """Test _XattrStub""" > + > + def testBasic(self): > + """Verify the stub is stubby""" > + # Would be nice to verify raised errno is OperationNotSupported. > + self.assertRaises(OSError, _XattrStub.get, '/', '') > + self.assertRaises(OSError, _XattrStub.set, '/', '', '') > + self.assertRaises(OSError, _XattrStub.get_all, '/') > + self.assertRaises(OSError, _XattrStub.remove, '/', '') > + self.assertRaises(OSError, _XattrStub.list, '/') > + > + > +class StandardTest(TestCase): > + """Test basic xattr API""" > + > + MODULES =3D (_xattr, _XattrSystemCommands, _XattrStub) > + FUNCS =3D ('get', 'get_all', 'set', 'remove', 'list') > + > + def testApi(self): > + """Make sure the exported API matches""" > + for mod in self.MODULES: > + for f in self.FUNCS: > + self.assertTrue(hasattr(mod, f), > + '%s func missing in %s' % (f, mod)) > diff --git a/pym/portage/util/_xattr.py b/pym/portage/util/_xattr.py > new file mode 100644 > index 0000000..db3cf6e > --- /dev/null > +++ b/pym/portage/util/_xattr.py > @@ -0,0 +1,205 @@ > +# Copyright 2010-2013 Gentoo Foundation > +# Distributed under the terms of the GNU General Public License v2 > + > +"""Portability shim for xattr support > + > +Exported API is the xattr object with get/get_all/set/remove/list operat= ions. > + > +See the standard xattr module for more documentation. > +""" > + > +from __future__ import print_function > + > +import contextlib > +import os > +import subprocess > + > +from portage.exception import OperationNotSupported > + > + > +class _XattrGetAll(object): > + """Implement get_all() using list()/get() if there is no easy bulk meth= od""" > + > + @classmethod > + def get_all(cls, item, nofollow=3DFalse, namespace=3DNone): > + return [(name, cls.get(item, name, nofollow=3Dnofollow, namespace=3Dna= mespace)) > + for name in cls.list(item, nofollow=3Dnofollow, namespace=3Dna= mespace)] > + > + > +class _XattrSystemCommands(_XattrGetAll): > + """Implement things with getfattr/setfattr""" > + > + @staticmethod > + def _parse_output(output): > + for line in output.readlines(): > + if line.startswith(b'#'): > + continue > + line =3D line.rstrip() > + if not line: > + continue > + # The lines will have the format: > + # user.hex=3D0x12345 > + # user.base64=3D0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=3D > + # user.string=3D"value0" > + # But since we don't do interpretation on the value (we just > + # save & restore it), don't bother with decoding here. > + yield line.split(b'=3D', 1) > + > + @staticmethod > + def _call(*args, **kwargs): > + proc =3D subprocess.Popen(*args, **kwargs) > + proc.stdin.close() AttributeError: 'NoneType' object has no attribute 'close' If closing stdin is necessary, then I suggest: if proc.stdin is not None: proc.stdin.close() > + proc.wait() > + return proc > + > + @classmethod > + def get(cls, item, name, nofollow=3DFalse, namespace=3DNone): > + if namespace: > + name =3D '%s.%s' % (namespace, name) > + cmd =3D ['getfattr', '--absolute-names', '-n', name, item] > + if nofollow: > + cmd +=3D ['-h'] > + proc =3D cls._call(cmd, stdout=3Dsubprocess.PIPE, stderr=3Dsubprocess.= PIPE) > + > + value =3D None > + for _, value in cls._parse_output(proc.stdout): > + break > + > + proc.stdout.close() > + return value > + > + @classmethod > + def set(cls, item, name, value, _flags=3D0, namespace=3DNone): > + if namespace: > + name =3D '%s.%s' % (namespace, name) > + cmd =3D ['setfattr', '-n', name, '-v', value, item] > + cls._call(cmd) > + > + @classmethod > + def remove(cls, item, name, nofollow=3DFalse, namespace=3DNone): > + if namespace: > + name =3D '%s.%s' % (namespace, name) > + cmd =3D ['setfattr', '-x', name, item] > + if nofollow: > + cmd +=3D ['-h'] > + cls._call(cmd) > + > + @classmethod > + def list(cls, item, nofollow=3DFalse, namespace=3DNone, _names_only=3DT= rue): > + cmd =3D ['getfattr', '-d', '--absolute-names', item] > + if nofollow: > + cmd +=3D ['-h'] > + cmd +=3D ['-m', ('^%s[.]' % namespace) if namespace else ''] > + proc =3D cls._call(cmd, stdout=3Dsubprocess.PIPE, stderr=3Dsubprocess.= PIPE) > + > + ret =3D [] > + if namespace: > + namespace =3D '%s.' % namespace > + for name, value in cls._parse_output(proc.stdout): > + if namespace: > + if name.startswith(namespace): > + name =3D name[len(namespace):] > + else: > + continue > + if _names_only: > + ret.append(name) > + else: > + ret.append((name, value)) > + > + proc.stdout.close() > + return ret > + > + @classmethod > + def get_all(cls, item, nofollow=3DFalse, namespace=3DNone): > + return cls.list(item, nofollow=3Dnofollow, namespace=3Dnamespace, > + _names_only=3DFalse) > + > + > +# pylint: disable=3DW0613 > +class _XattrStub(_XattrGetAll): > + """Fake object since system doesn't support xattrs""" > + > + @staticmethod > + def _raise(): > + e =3D OSError('stub') > + e.errno =3D OperationNotSupported.errno > + raise e > + > + @classmethod > + def get(cls, item, name, nofollow=3DFalse, namespace=3DNone): > + cls._raise() > + > + @classmethod > + def set(cls, item, name, value, flags=3D0, namespace=3DNone): > + cls._raise() > + > + @classmethod > + def remove(cls, item, name, nofollow=3DFalse, namespace=3DNone): > + cls._raise() > + > + @classmethod > + def list(cls, item, nofollow=3DFalse, namespace=3DNone): > + cls._raise() > + > + > +if hasattr(os, 'getxattr'): > + # Easy as pie -- active python supports it. > + class xattr(_XattrGetAll): > + """Python >=3D3.3 and GNU/Linux""" > + get =3D os.getxattr > + set =3D os.setxattr > + remove =3D os.removexattr > + list =3D os.listxattr > + > +else: > + try: > + # Maybe we have the xattr module. > + import xattr > + > + except ImportError: > + try: > + # Maybe we have the attr package. > + with open(os.devnull, 'wb') as f: > + subprocess.call(['getfattr', '--version'], stdout=3Df) > + subprocess.call(['setfattr', '--version'], stdout=3Df) > + xattr =3D _XattrSystemCommands > + > + except OSError: > + # Stub it out completely. > + xattr =3D _XattrStub > + > + > +@contextlib.contextmanager > +def preserve_xattrs(path, nofollow=3DFalse, namespace=3DNone): > + """Context manager to save/restore extended attributes on |path| > + > + If you want to rewrite a file (possibly replacing it with a new one), b= ut > + want to preserve the extended attributes, this will do the trick. > + > + # First read all the extended attributes. > + with save_xattrs('/some/file'): > + ... rewrite the file ... > + # Now the extended attributes are restored as needed. > + """ > + kwargs =3D {'nofollow': nofollow,} > + if namespace: > + # Compiled xattr python module does not like it when namespace=3DNone. > + kwargs['namespace'] =3D namespace > + > + old_attrs =3D dict(xattr.get_all(path, **kwargs)) > + try: > + yield > + finally: > + new_attrs =3D dict(xattr.get_all(path, **kwargs)) > + for name, value in new_attrs.iteritems(): > + if name not in old_attrs: > + # Clear out new ones. > + xattr.remove(path, name, **kwargs) > + elif new_attrs[name] !=3D old_attrs[name]: > + # Update changed ones. > + xattr.set(path, name, value, **kwargs) > + > + for name, value in old_attrs.iteritems(): > + if name not in new_attrs: > + # Re-add missing ones. > + xattr.set(path, name, value, **kwargs) > diff --git a/pym/portage/util/movefile.py b/pym/portage/util/movefile.py > index 452e77f..20859fe 100644 > --- a/pym/portage/util/movefile.py > +++ b/pym/portage/util/movefile.py > @@ -11,7 +11,6 @@ import os as _os > import shutil as _shutil > import stat > import sys > -import subprocess > import textwrap > =20 > import portage > @@ -23,6 +22,7 @@ from portage.exception import OperationNotSupported > from portage.localization import _ > from portage.process import spawn > from portage.util import writemsg > +from portage.util._xattr import xattr > =20 > def _apply_stat(src_stat, dest): > _os.chown(dest, src_stat.st_uid, src_stat.st_gid) > @@ -68,86 +68,32 @@ class _xattr_excluder(object): > =20 > return False > =20 > -if hasattr(_os, "getxattr"): > - # Python >=3D3.3 and GNU/Linux > - def _copyxattr(src, dest, exclude=3DNone): > - > - try: > - attrs =3D _os.listxattr(src) > - except OSError as e: > - if e.errno !=3D OperationNotSupported.errno: > - raise > - attrs =3D () > - if attrs: > - if exclude is not None and isinstance(attrs[0], bytes): > - exclude =3D exclude.encode(_encodings['fs']) > - exclude =3D _get_xattr_excluder(exclude) > - > - for attr in attrs: > - if exclude(attr): > - continue > - try: > - _os.setxattr(dest, attr, _os.getxattr(src, attr)) > - raise_exception =3D False > - except OSError: > - raise_exception =3D True > - if raise_exception: > - raise OperationNotSupported(_("Filesystem containing file '%s' " > - "does not support extended attribute '%s'") % > - (_unicode_decode(dest), _unicode_decode(attr))) > -else: > +def _copyxattr(src, dest, exclude=3DNone): > + """Copy the extended attributes from |src| to |dest|""" > try: > - import xattr > - except ImportError: > - xattr =3D None > - if xattr is not None: > - def _copyxattr(src, dest, exclude=3DNone): > - > - try: > - attrs =3D xattr.list(src) > - except IOError as e: > - if e.errno !=3D OperationNotSupported.errno: > - raise > - attrs =3D () > + attrs =3D xattr.list(src) > + except (OSError, IOError) as e: > + if e.errno !=3D OperationNotSupported.errno: > + raise > + attrs =3D () > =20 > - if attrs: > - if exclude is not None and isinstance(attrs[0], bytes): > - exclude =3D exclude.encode(_encodings['fs']) > - exclude =3D _get_xattr_excluder(exclude) > + if attrs: > + if exclude is not None and isinstance(attrs[0], bytes): > + exclude =3D exclude.encode(_encodings['fs']) > + exclude =3D _get_xattr_excluder(exclude) > =20 > - for attr in attrs: > - if exclude(attr): > - continue > - try: > - xattr.set(dest, attr, xattr.get(src, attr)) > - raise_exception =3D False > - except IOError: > - raise_exception =3D True > - if raise_exception: > - raise OperationNotSupported(_("Filesystem containing file '%s' " > - "does not support extended attribute '%s'") % > - (_unicode_decode(dest), _unicode_decode(attr))) > - else: > + for attr in attrs: > + if exclude(attr): > + continue > try: > - with open(_os.devnull, 'wb') as f: > - subprocess.call(["getfattr", "--version"], stdout=3Df) > - subprocess.call(["setfattr", "--version"], stdout=3Df) > - except OSError: > - def _copyxattr(src, dest, exclude=3DNone): > - # TODO: implement exclude > - getfattr_process =3D subprocess.Popen(["getfattr", "-d", "--absolute= =2Dnames", src], stdout=3Dsubprocess.PIPE) > - getfattr_process.wait() > - extended_attributes =3D getfattr_process.stdout.readlines() > - getfattr_process.stdout.close() > - if extended_attributes: > - extended_attributes[0] =3D b"# file: " + _unicode_encode(dest) + b"= \n" > - setfattr_process =3D subprocess.Popen(["setfattr", "--restore=3D-"]= , stdin=3Dsubprocess.PIPE, stderr=3Dsubprocess.PIPE) > - setfattr_process.communicate(input=3Db"".join(extended_attributes)) > - if setfattr_process.returncode !=3D 0: > - raise OperationNotSupported("Filesystem containing file '%s' does = not support extended attributes" % dest) > - else: > - def _copyxattr(src, dest, exclude=3DNone): > - pass > + xattr.set(dest, attr, xattr.get(src, attr)) > + raise_exception =3D False > + except (OSError, IOError): > + raise_exception =3D True > + if raise_exception: > + raise OperationNotSupported(_("Filesystem containing file '%s' " > + "does not support extended attribute '%s'") % > + (_unicode_decode(dest), _unicode_decode(attr))) > =20 > def movefile(src, dest, newmtime=3DNone, sstat=3DNone, mysettings=3DNone, > hardlink_candidates=3DNone, encoding=3D_encodings['fs']): >=20 =2D- Arfrever Frehtes Taifersar Arahesis --nextPart2343426.czUQRC1VY1 Content-Type: application/pgp-signature; name=signature.asc Content-Description: This is a digitally signed message part. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABCgAGBQJSZqMeAAoJEA1v8+vOu0V0V5gQALhRh34B6ga6xvWrrRwLI8Ub roFb+FtiYbr4Efp+MsR+pLvOdyhZDiibS5Jr4GPbBOh/fdNZxN8fPlDIvKluWGgI u54LJ1e37B4D2c7ich6z/P35uM10rswpnEGpMWryEIy024xktoCRdc3HDTfa9enF wwCwry6dvnAuT7NmcDDFVjdIvqI2Ou5RRzHZCqsKZwZ+KtpFK91FLx2B3fmJydjM VVDcXNFsm59UPu3pTCSPotTd7fgxfguuStqrvwF7OAA/5i0jNDQB8V1IktRMDpK6 19IxmmWc0hSszJoJa4o7iCLlvZbHD/3sR2lHPgABl/Vd1dH2NS8gJAXxzqIudOCY i0RZrSKv3OCnjMG6mEr8Qv3GOfqnkPCHbnm6gCrU4cqY7z0r3OTjz1u+MUjlpMrV 6FJq8/Zx7QX3/57uWJw5fbH4kMTj6VUU716vTAFJ4Xpu0KtPZL87Q21DtFvkh/KL KbkZax8PgakSl3HylDHqJ/DP2djLwoWldDfd3GPdxy1KX0i0MTIMgupWaG7NQa8i pttLzv+2n1vD2FwG7Z3rCKjs89vsQeBTnj/EsdIv+pgCXPQlIqpB09iuHCVqt2EO XYpa+OMH86q2UzjRgz58nfADKlD8SHWoFVFXsq5rKWFFcEKgNqo+6mnNURljyBRr GfR6X+lAHndirreyKXDj =4niz -----END PGP SIGNATURE----- --nextPart2343426.czUQRC1VY1--