Subject: namei() for binary emulations
To: None <tech-kern@netbsd.org>
From: Quentin Garnier <cube@cubidou.net>
List: tech-kern
Date: 07/04/2005 01:35:13
--APlYHCtpeOhspHkB
Content-Type: multipart/mixed; boundary="gneEPciiIl/aKvOT"
Content-Disposition: inline
--gneEPciiIl/aKvOT
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Hi folks,
I think I've raised that same subject quite a while ago already, and
now that I have a solution that might be seen as a good compromise by
everybody, I'm coming back with it.
For emulated binaries, some syscalls use emul_find() for name
resolutions because there are files that need to be found inside the
emulation root, although they are referred from / by the binary
itself.
emul_find() is currently implemented by looking for a file from the
emulation root, and trying from the real root in case it's not found.
However, that method fails when absolute links are involved in the
path resolution: those are handled by namei(), called by emul_find().
namei() doesn't know about emulation roots, thus consider all absolute
links as referring the real root.
While it doesn't usually break very foreign emulations such as Linux,
it is not the same for COMPAT_NETBSD32: binaries in /usr/bin have
/usr/libexec/ld.so_elf as their interpreter, which is an absolute
link to /libexec/ld.so_elf. Therefore, executing a binary from
/emul/netbsd32/usr/bin fails, because emul_find() finds the ld.so_elf
from the native system, not the netbsd32 one.
My proposal is to have a special namei(), named compat_namei() that
will know about an alternate root and do the right thing when
stumbling upon an absolute link: that is, first trying to resolve
it from the alternate root.
There are issues with this approach: either we bloat namei(), or we
copy it elsewhere in the tree. The former is not an option: we don't
want to slow down namei(). The latter will prove very hard to maintain.
Therefore, I suggest splitting namei() itself into its own file, with
the COMPAT_* parts inside conditionals, so that we can use the same
source file to compile the other version we need for libcompat.
I'm attaching that file, and the diff against emul_find() to use it.
As you will see, it is not a perfect solution: it decreases a lot
namei()'s readability, although it is already a complex function.
Comments?
--=20
Quentin Garnier - cube@cubidou.net - cube@NetBSD.org
"When I find the controls, I'll go where I like, I'll know where I want
to be, but maybe for now I'll stay right here on a silent sea."
KT Tunstall, Silent Sea, Eye to the Telescope, 2004.
--gneEPciiIl/aKvOT
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="vfs_namei.c"
/* $NetBSD$ */
/*
* Copyright (c) 1982, 1986, 1989, 1993
* The Regents of the University of California. All rights reserved.
* (c) UNIX System Laboratories, Inc.
* All or some portions of this file are derived from material licensed
* to the University of California by American Telephone and Telegraph
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
* the permission of UNIX System Laboratories, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)vfs_lookup.c 8.10 (Berkeley) 5/27/95
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");
#ifdef _KERNEL_OPT
#include "opt_ktrace.h"
#include "opt_systrace.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/filedesc.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#ifdef KTRACE
#include <sys/ktrace.h>
#endif
#ifdef SYSTRACE
#include <sys/systrace.h>
#endif
#ifdef COMPAT_NAMEI
#include <compat/common/compat_namei.h>
#endif
/*
* Convert a pathname into a pointer to a locked inode.
*
* The FOLLOW flag is set when symbolic links are to be followed
* when they occur at the end of the name translation process.
* Symbolic links are always followed for all other pathname
* components other than the last.
*
* The segflg defines whether the name is to be copied from user
* space or kernel space.
*
* Overall outline of namei:
*
* copy in name
* get starting directory
* while (!done && !error) {
* call lookup to search path.
* if symbolic link, massage name in buffer and continue
* }
*/
int
#ifdef COMPAT_NAMEI
compat_namei(struct compat_nameidata *cndp)
#else
namei(struct nameidata *ndp)
#endif
{
struct cwdinfo *cwdi; /* pointer to cwd state */
char *cp; /* pointer into pathname argument */
struct vnode *dp; /* the directory we are searching */
struct iovec aiov; /* uio for reading symbolic links */
struct uio auio;
int error, linklen;
#ifdef COMPAT_NAMEI
int restart;
size_t pathlen = 0;
const char *next = NULL;
u_long loopcnt = 0;
struct nameidata *ndp = &cndp->cni_ni;
#endif
struct componentname *cnp = &ndp->ni_cnd;
#ifdef DIAGNOSTIC
if (!cnp->cn_cred || !cnp->cn_proc)
panic("namei: bad cred/proc");
if (cnp->cn_nameiop & (~OPMASK))
panic("namei: nameiop contaminated with flags");
if (cnp->cn_flags & OPMASK)
panic("namei: flags contaminated with nameiops");
#endif
cwdi = cnp->cn_proc->p_cwdi;
/*
* Get a buffer for the name to be translated, and copy the
* name into the buffer.
*/
if ((cnp->cn_flags & HASBUF) == 0)
cnp->cn_pnbuf = PNBUF_GET();
if (ndp->ni_segflg == UIO_SYSSPACE)
error = copystr(ndp->ni_dirp, cnp->cn_pnbuf,
MAXPATHLEN, &ndp->ni_pathlen);
else
error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf,
MAXPATHLEN, &ndp->ni_pathlen);
/*
* POSIX.1 requirement: "" is not a valid file name.
*/
if (!error && ndp->ni_pathlen == 1)
error = ENOENT;
if (error) {
PNBUF_PUT(cnp->cn_pnbuf);
ndp->ni_vp = NULL;
return (error);
}
ndp->ni_loopcnt = 0;
#ifdef KTRACE
if (KTRPOINT(cnp->cn_proc, KTR_NAMEI))
ktrnamei(cnp->cn_proc, cnp->cn_pnbuf);
#endif
#ifdef SYSTRACE
if (ISSET(cnp->cn_proc->p_flag, P_SYSTRACE))
systrace_namei(ndp);
#endif
/*
* Get starting point for the translation.
*/
if ((ndp->ni_rootdir = cwdi->cwdi_rdir) == NULL)
ndp->ni_rootdir = rootvnode;
/*
* Check if starting from root directory or current directory.
*/
if (cnp->cn_pnbuf[0] == '/') {
#ifdef COMPAT_NAMEI
/*
* COMPAT_NAMEI SPECIFIC
*
* The COMPAT_NAMEI_ALTROOT flag is used to tell which root
* we're using for the lookup. As we're in the compat module,
* we always want to start with the alternative root dir. But
* we don't necessarily have to, as we may be asked for a
* relative path.
*/
dp = cndp->cni_altrootdir;
cndp->cni_flags |= COMPAT_NAMEI_ALTROOT;
#else
dp = ndp->ni_rootdir;
#endif /* COMPAT_NAMEI */
VREF(dp);
} else {
dp = cwdi->cwdi_cdir;
VREF(dp);
#ifdef COMPAT_NAMEI
cndp->cni_flags &= ~COMPAT_NAMEI_ALTROOT;
#endif /* COMPAT_NAMEI */
}
#ifdef COMPAT_NAMEI
/*
* COMPAT_NAMEI SPECIFIC
*
* We want to keep the full resolution of the path. As an
* intermediate step in the name resolution, we mostly want
* to know if the file exists inside the alternative root. The
* actual vnode is not our main target.
*
* Keeping the full path (even at the cost of leaving symlinks
* in the middle, we'll do the resolution again later anyway)
* means we cannot simply erase the current path when we get
* a symlink. Therefore cn_nameptr must be handled with care,
* as we might start from the middle of the string cn_pnbuf
* points to.
*/
cnp->cn_nameptr = cnp->cn_pnbuf;
#endif /* COMPAT_NAMEI */
for (;;) {
#ifdef COMPAT_NAMEI
redolookup:
#endif /* COMPAT_NAMEI */
if (!dp->v_mount)
{
/* Give up if the directory is no longer mounted */
PNBUF_PUT(cnp->cn_pnbuf);
return (ENOENT);
}
#ifndef COMPAT_NAMEI
cnp->cn_nameptr = cnp->cn_pnbuf;
#endif /* !COMPAT_NAMEI */
ndp->ni_startdir = dp;
#ifdef COMPAT_NAMEI
/*
* COMPAT_NAMEI SPECIFIC
*
* There might be another lookup, starting from the real
* root dir in case we haven't found the target in the
* alternative root. So we have to keep around a few
* values to start again.
*/
if ((cndp->cni_flags & COMPAT_NAMEI_ALTROOT) != 0) {
/* Save state in case we have to back out */
next = ndp->ni_next;
pathlen = ndp->ni_pathlen;
loopcnt = ndp->ni_loopcnt;
}
#endif /* COMPAT_NAMEI */
if ((error = lookup(ndp)) != 0) {
#ifdef COMPAT_NAMEI
if ((cndp->cni_flags & COMPAT_NAMEI_ALTROOT) != 0) {
cndp->cni_flags &= ~COMPAT_NAMEI_ALTROOT;
ndp->ni_next = next;
ndp->ni_pathlen = pathlen;
ndp->ni_loopcnt = loopcnt;
dp = ndp->ni_rootdir;
VREF(dp);
goto redolookup;
}
#endif /* COMPAT_NAMEI */
PNBUF_PUT(cnp->cn_pnbuf);
return (error);
}
/*
* Check for symbolic link
*/
if ((cnp->cn_flags & ISSYMLINK) == 0) {
if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0)
PNBUF_PUT(cnp->cn_pnbuf);
else
cnp->cn_flags |= HASBUF;
return (0);
}
if ((cnp->cn_flags & LOCKPARENT) && (cnp->cn_flags & ISLASTCN))
VOP_UNLOCK(ndp->ni_dvp, 0);
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
error = ELOOP;
break;
}
if (ndp->ni_vp->v_mount->mnt_flag & MNT_SYMPERM) {
error = VOP_ACCESS(ndp->ni_vp, VEXEC, cnp->cn_cred,
cnp->cn_proc);
if (error != 0)
break;
}
#ifdef COMPAT_NAMEI
/*
* COMPAT_NAMEI SPECIFIC
*
* The real namei() allocates a new pnbuf only if there
* are remaining path components, so it can copy them after
* the target of the current symlink.
*
* When it is the last component, namei() does not allocate
* a pnbuf, and simply overwrite the current pnbuf. We
* cannot afford that here since we want to keep the first
* part of the name resolution, so we unconditionally
* allocate a pnbuf.
*
* Of course, if it is an absolute symlink we will overwrite
* the current pnbuf, but we can't know that before reading
* it.
*/
cp = PNBUF_GET();
#else
if (ndp->ni_pathlen > 1)
cp = PNBUF_GET();
else
cp = cnp->cn_pnbuf;
#endif /* COMPAT_NAMEI */
aiov.iov_base = cp;
aiov.iov_len = MAXPATHLEN;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_offset = 0;
auio.uio_rw = UIO_READ;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_procp = NULL;
auio.uio_resid = MAXPATHLEN;
error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred);
if (error) {
badlink:
#ifndef COMPAT_NAMEI
/*
* Under COMPAT_NAMEI, cp has been unconditionaly
* allocated
*/
if (ndp->ni_pathlen > 1)
#endif /* !COMPAT_NAMEI */
PNBUF_PUT(cp);
break;
}
linklen = MAXPATHLEN - auio.uio_resid;
if (linklen == 0) {
error = ENOENT;
goto badlink;
}
/*
* Do symlink substitution, if appropriate, and
* check length for potential overflow.
*
* COMPAT_NAMEI SPECIFIC
*
* We keep the resolved path, so we have to take it in account if the
* symlink is relative. In case the symlink is absolute, we'll
* overwrite the current pnbuf so it can be longer.
*/
if (((ndp->ni_vp->v_mount->mnt_flag & MNT_MAGICLINKS) &&
symlink_magic(cp, &linklen)) ||
#ifdef COMPAT_NAMEI
(linklen + ndp->ni_pathlen +
(cp[0] == '/' ? (cnp->cn_nameptr - cnp->cn_pnbuf) : 0) >= MAXPATHLEN)) {
#else
(linklen + ndp->ni_pathlen >= MAXPATHLEN)) {
#endif /* COMPAT_NAMEI */
error = ENAMETOOLONG;
goto badlink;
}
#ifdef COMPAT_NAMEI
/*
* COMPAT_NAMEI SPECIFIC
*
* We want to keep the full path of the target in the pnbuf,
* so we do whatever is necessary at that point: we start by
* copying the rest of the path after the resolution of the
* symlink. Then, if it is an absolute symlink, we erase the
* current pnbuf, but if it is a relative one, we copy the
* content of cp after the last resolved component of the
* path. That means we can end with '/../' in the path.
*
* While there, take note in 'restart' if it is an absolute
* symlink: we'll have to restart the resolution from the
* alternative root.
*/
memcpy(cp + linklen, ndp->ni_next, ndp->ni_pathlen);
if (cp[0] == '/') {
PNBUF_PUT(cnp->cn_pnbuf);
cnp->cn_pnbuf = cp;
restart = 1;
} else {
memcpy(__UNCONST(cnp->cn_nameptr), cp, linklen+ndp->ni_pathlen);
PNBUF_PUT(cp);
restart = 0;
}
#else
if (ndp->ni_pathlen > 1) {
memcpy(cp + linklen, ndp->ni_next, ndp->ni_pathlen);
PNBUF_PUT(cnp->cn_pnbuf);
cnp->cn_pnbuf = cp;
} else
cnp->cn_pnbuf[linklen] = '\0';
#endif /* COMPAT_NAMEI */
ndp->ni_pathlen += linklen;
vput(ndp->ni_vp);
dp = ndp->ni_dvp;
/*
* Check if root directory should replace current directory.
*/
#ifdef COMPAT_NAMEI
if (restart) {
vrele(dp);
dp = cndp->cni_altrootdir;
cndp->cni_flags |= COMPAT_NAMEI_ALTROOT;
VREF(dp);
cnp->cn_nameptr = cnp->cn_pnbuf;
}
#else
if (cnp->cn_pnbuf[0] == '/') {
vrele(dp);
dp = ndp->ni_rootdir;
VREF(dp);
}
#endif /* COMPAT_NAMEI */
}
PNBUF_PUT(cnp->cn_pnbuf);
vrele(ndp->ni_dvp);
vput(ndp->ni_vp);
ndp->ni_vp = NULL;
return (error);
}
--gneEPciiIl/aKvOT
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="compat.diff"
Content-Transfer-Encoding: quoted-printable
Index: compat_util.c
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
RCS file: /rep/NetBSD-src/cvs/src/sys/compat/common/compat_util.c,v
retrieving revision 1.29
diff -u -r1.29 compat_util.c
--- compat_util.c 2005/05/29 22:08:16 1.29
+++ compat_util.c 2005/07/03 22:42:37
@@ -56,6 +56,7 @@
=20
=20
#include <compat/common/compat_util.h>
+#include <compat/common/compat_namei.h>
=20
/*
* Search an alternate path before passing pathname arguments on
@@ -80,7 +81,8 @@
const char **pbuf;
int sflag;
{
- struct nameidata nd;
+ struct compat_nameidata cnd;
+ struct nameidata *ndp =3D &cnd.cni_ni;
struct nameidata ndroot;
struct vattr vat;
struct vattr vatroot;
@@ -139,22 +141,36 @@
;
*cp =3D '\0';
=20
- NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, tbuf, p);
+ NDINIT(ndp, LOOKUP, FOLLOW, UIO_SYSSPACE, tbuf, p);
=20
- if ((error =3D namei(&nd)) !=3D 0)
+ if ((error =3D namei(ndp)) !=3D 0)
goto bad;
=20
*cp =3D '/';
break;
case CHECK_ALT_FL_EXISTS:
case CHECK_ALT_FL_SYMLINK:
- NDINIT(&nd, LOOKUP,
- (sflag =3D=3D CHECK_ALT_FL_SYMLINK) ? NOFOLLOW : FOLLOW,
- UIO_SYSSPACE, tbuf, p);
+ NDINIT(&ndroot, LOOKUP, FOLLOW, UIO_SYSSPACE, prefix, p);
=20
- if ((error =3D namei(&nd)) !=3D 0)
+ if ((error =3D namei(&ndroot)) !=3D 0)
goto bad;
=20
+ /* We use SAVENAME because the returned nameidata structure will
+ * contain the full path of the target, potentially relative to
+ * the emul root. */
+ NDINIT(ndp, LOOKUP,=09
+ ((sflag =3D=3D CHECK_ALT_FL_SYMLINK) ? NOFOLLOW : FOLLOW) | SAVENAME,
+ UIO_SYSSPACE, ptr, p);
+ cnd.cni_altrootdir =3D ndroot.ni_vp;
+
+ error =3D compat_namei(&cnd);
+ if (error !=3D 0)
+ goto bad2;
+ if ((cnd.cni_flags & COMPAT_NAMEI_ALTROOT) =3D=3D 0)
+ ptr =3D tbuf;
+ copystr(cnd.cni_ni.ni_cnd.cn_pnbuf, ptr, sz, &len);
+ PNBUF_PUT(cnd.cni_ni.ni_cnd.cn_pnbuf);
+
/*
* We now compare the vnode of the emulation root to the one
* vnode asked. If they resolve to be the same, then we
@@ -163,12 +179,7 @@
* root directory and never finding it, because "/" resolves
* to the emulation root directory. This is expensive :-(
*/
- NDINIT(&ndroot, LOOKUP, FOLLOW, UIO_SYSSPACE, prefix, p);
-
- if ((error =3D namei(&ndroot)) !=3D 0)
- goto bad2;
-
- if ((error =3D VOP_GETATTR(nd.ni_vp, &vat, p->p_ucred, p)) !=3D 0)
+ if ((error =3D VOP_GETATTR(ndp->ni_vp, &vat, p->p_ucred, p)) !=3D 0)
goto bad3;
=20
if ((error =3D VOP_GETATTR(ndroot.ni_vp, &vatroot, p->p_ucred, p))
@@ -184,7 +195,7 @@
break;
}
=20
- vrele(nd.ni_vp);
+ vrele(ndp->ni_vp);
if (sflag =3D=3D CHECK_ALT_FL_EXISTS)
vrele(ndroot.ni_vp);
=20
@@ -208,9 +219,9 @@
return 0;
=20
bad3:
- vrele(ndroot.ni_vp);
+ vrele(ndp->ni_vp);
bad2:
- vrele(nd.ni_vp);
+ vrele(ndroot.ni_vp);
bad:
free(tbuf, M_TEMP);
return error;
--gneEPciiIl/aKvOT
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="compat_namei.h"
/* $NetBSD$ */
/*
* Copyright (c) 2005 The NetBSD Foundation.
* All rights reserved.
*
* This code is derived from software contributed to the NetBSD Foundation
* by Quentin Garnier.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _COMPAT_NAMEI_H_
struct compat_nameidata {
struct nameidata cni_ni;
struct vnode *cni_altrootdir;
int cni_flags;
#define COMPAT_NAMEI_ALTROOT 0x1
};
int compat_namei(struct compat_nameidata *);
#endif /* !_COMPAT_NAMEI_H_ */
--gneEPciiIl/aKvOT--
--APlYHCtpeOhspHkB
Content-Type: application/pgp-signature
Content-Disposition: inline
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (NetBSD)
iQEVAwUBQsh2MdgoQloHrPnoAQLJ0Af8DgOoe1ad+CeUxPlUZxTfltIcIZPPp3C8
r0HOCPvKlA8McZUTkwBT3UTYUkcTxPqMPcqADOVG6Nu8qIfpMsp4xXbuq7FK/JeI
ZbjYE1pRbkz6F/rtl268OK199OLAC5MMJrLqYZz2csvq5St3j7PS8LUKIis/GQpm
ZTxb1fd2oNz33eWFyZQI5JdOXNvP0NnXf8QVfFEEiXD2JNbrVFblTkfhlWnx2jfn
dgUP8O4YjTqXYPDvS1sxy89k1myUPAaFbEAFwDLp/FUoJGmJDSgQHXPMA0GIozW1
CKT4T3YYXiAXy4ZS6D3ZoD72+vb1F8WiJqRPoj8OOD7bhTZ3wOsMHw==
=bUhq
-----END PGP SIGNATURE-----
--APlYHCtpeOhspHkB--