summaryrefslogtreecommitdiffstats
path: root/lib/libarchive
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2009-04-27 18:27:54 +0000
committerkientzle <kientzle@FreeBSD.org>2009-04-27 18:27:54 +0000
commit8958ccc8b0d4ae23fed5df59edf3791d898978f1 (patch)
tree1e1f0a336858e45c992e396f0dd4b9197a9c3969 /lib/libarchive
parent0dffd8719168deb07b1f0cc8db36e3948f969cbb (diff)
downloadFreeBSD-src-8958ccc8b0d4ae23fed5df59edf3791d898978f1.zip
FreeBSD-src-8958ccc8b0d4ae23fed5df59edf3791d898978f1.tar.gz
Merge r1053,r1055,r1056,r1057,r1065 from libarchive.googlecode.com:
* Fix parsing of POSIX.1e ACLs from Solaris tar archives * Test the above * Preserve the order of POSIX.1e ACL entries * Update tests whose results depended on the order of ACL entries * Identify NFSv4 ACLs in Solaris tar archives and warn that they're not yet supported. (In particular, don't try to parse them as POSIX.1e ACLs.) Thanks to: Edward Napierala sent me some Solaris 10 tar archives to test
Diffstat (limited to 'lib/libarchive')
-rw-r--r--lib/libarchive/archive_entry.c115
-rw-r--r--lib/libarchive/archive_read_support_format_tar.c47
-rw-r--r--lib/libarchive/test/Makefile1
-rw-r--r--lib/libarchive/test/test_acl_pax.c10
-rw-r--r--lib/libarchive/test/test_compat_solaris_tar_acl.c128
-rw-r--r--lib/libarchive/test/test_compat_solaris_tar_acl.tar.uu61
6 files changed, 314 insertions, 48 deletions
diff --git a/lib/libarchive/archive_entry.c b/lib/libarchive/archive_entry.c
index cf61067..16166aa 100644
--- a/lib/libarchive/archive_entry.c
+++ b/lib/libarchive/archive_entry.c
@@ -115,6 +115,7 @@ static int acl_special(struct archive_entry *entry,
static struct ae_acl *acl_new_entry(struct archive_entry *entry,
int type, int permset, int tag, int id);
static int isint_w(const wchar_t *start, const wchar_t *end, int *result);
+static int ismode_w(const wchar_t *start, const wchar_t *end, int *result);
static void next_field_w(const wchar_t **wp, const wchar_t **start,
const wchar_t **end, wchar_t *sep);
static int prefix_w(const wchar_t *start, const wchar_t *end,
@@ -1238,7 +1239,7 @@ static struct ae_acl *
acl_new_entry(struct archive_entry *entry,
int type, int permset, int tag, int id)
{
- struct ae_acl *ap;
+ struct ae_acl *ap, *aq;
if (type != ARCHIVE_ENTRY_ACL_TYPE_ACCESS &&
type != ARCHIVE_ENTRY_ACL_TYPE_DEFAULT)
@@ -1251,20 +1252,26 @@ acl_new_entry(struct archive_entry *entry,
/* XXX TODO: More sanity-checks on the arguments XXX */
/* If there's a matching entry already in the list, overwrite it. */
- for (ap = entry->acl_head; ap != NULL; ap = ap->next) {
+ ap = entry->acl_head;
+ aq = NULL;
+ while (ap != NULL) {
if (ap->type == type && ap->tag == tag && ap->id == id) {
ap->permset = permset;
return (ap);
}
+ aq = ap;
+ ap = ap->next;
}
- /* Add a new entry to the list. */
+ /* Add a new entry to the end of the list. */
ap = (struct ae_acl *)malloc(sizeof(*ap));
if (ap == NULL)
return (NULL);
memset(ap, 0, sizeof(*ap));
- ap->next = entry->acl_head;
- entry->acl_head = ap;
+ if (aq == NULL)
+ entry->acl_head = ap;
+ else
+ aq->next = ap;
ap->type = type;
ap->tag = tag;
ap->id = id;
@@ -1586,11 +1593,10 @@ __archive_entry_acl_parse_w(struct archive_entry *entry,
struct {
const wchar_t *start;
const wchar_t *end;
- } field[4];
+ } field[4], name;
int fields;
int type, tag, permset, id;
- const wchar_t *p;
wchar_t sep;
while (text != NULL && *text != L'\0') {
@@ -1609,9 +1615,6 @@ __archive_entry_acl_parse_w(struct archive_entry *entry,
++fields;
} while (sep == L':');
- if (fields < 3)
- return (ARCHIVE_WARN);
-
/* Check for a numeric ID in field 1 or 3. */
id = -1;
isint_w(field[1].start, field[1].end, &id);
@@ -1619,27 +1622,6 @@ __archive_entry_acl_parse_w(struct archive_entry *entry,
if (id == -1 && fields > 3)
isint_w(field[3].start, field[3].end, &id);
- /* Parse the permissions from field 2. */
- permset = 0;
- p = field[2].start;
- while (p < field[2].end) {
- switch (*p++) {
- case 'r': case 'R':
- permset |= ARCHIVE_ENTRY_ACL_READ;
- break;
- case 'w': case 'W':
- permset |= ARCHIVE_ENTRY_ACL_WRITE;
- break;
- case 'x': case 'X':
- permset |= ARCHIVE_ENTRY_ACL_EXECUTE;
- break;
- case '-':
- break;
- default:
- return (ARCHIVE_WARN);
- }
- }
-
/*
* Solaris extension: "defaultuser::rwx" is the
* default ACL corresponding to "user::rwx", etc.
@@ -1651,22 +1633,47 @@ __archive_entry_acl_parse_w(struct archive_entry *entry,
} else
type = default_type;
+ name.start = name.end = NULL;
if (prefix_w(field[0].start, field[0].end, L"user")) {
- if (id != -1 || field[1].start < field[1].end)
+ if (!ismode_w(field[2].start, field[2].end, &permset))
+ return (ARCHIVE_WARN);
+ if (id != -1 || field[1].start < field[1].end) {
tag = ARCHIVE_ENTRY_ACL_USER;
- else
+ name = field[1];
+ } else
tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
} else if (prefix_w(field[0].start, field[0].end, L"group")) {
- if (id != -1 || field[1].start < field[1].end)
+ if (!ismode_w(field[2].start, field[2].end, &permset))
+ return (ARCHIVE_WARN);
+ if (id != -1 || field[1].start < field[1].end) {
tag = ARCHIVE_ENTRY_ACL_GROUP;
- else
+ name = field[1];
+ } else
tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
} else if (prefix_w(field[0].start, field[0].end, L"other")) {
- if (id != -1 || field[1].start < field[1].end)
+ if (fields == 2
+ && field[1].start < field[1].end
+ && ismode_w(field[1].start, field[2].end, &permset)) {
+ /* This is Solaris-style "other:rwx" */
+ } else if (fields == 3
+ && field[1].start == field[1].end
+ && field[2].start < field[2].end
+ && ismode_w(field[2].start, field[2].end, &permset)) {
+ /* This is FreeBSD-style "other::rwx" */
+ } else
return (ARCHIVE_WARN);
tag = ARCHIVE_ENTRY_ACL_OTHER;
} else if (prefix_w(field[0].start, field[0].end, L"mask")) {
- if (id != -1 || field[1].start < field[1].end)
+ if (fields == 2
+ && field[1].start < field[1].end
+ && ismode_w(field[1].start, field[1].end, &permset)) {
+ /* This is Solaris-style "mask:rwx" */
+ } else if (fields == 3
+ && field[1].start == field[1].end
+ && field[2].start < field[2].end
+ && ismode_w(field[2].start, field[2].end, &permset)) {
+ /* This is FreeBSD-style "mask::rwx" */
+ } else
return (ARCHIVE_WARN);
tag = ARCHIVE_ENTRY_ACL_MASK;
} else
@@ -1674,7 +1681,7 @@ __archive_entry_acl_parse_w(struct archive_entry *entry,
/* Add entry to the internal list. */
archive_entry_acl_add_entry_w_len(entry, type, permset,
- tag, id, field[1].start, field[1].end - field[1].start);
+ tag, id, name.start, name.end - name.start);
}
return (ARCHIVE_OK);
}
@@ -1798,6 +1805,38 @@ isint_w(const wchar_t *start, const wchar_t *end, int *result)
}
/*
+ * Parse a string as a mode field. Returns true if
+ * the string is non-empty and consists only of mode characters,
+ * false otherwise.
+ */
+static int
+ismode_w(const wchar_t *start, const wchar_t *end, int *permset)
+{
+ const wchar_t *p;
+
+ p = start;
+ *permset = 0;
+ while (p < end) {
+ switch (*p++) {
+ case 'r': case 'R':
+ *permset |= ARCHIVE_ENTRY_ACL_READ;
+ break;
+ case 'w': case 'W':
+ *permset |= ARCHIVE_ENTRY_ACL_WRITE;
+ break;
+ case 'x': case 'X':
+ *permset |= ARCHIVE_ENTRY_ACL_EXECUTE;
+ break;
+ case '-':
+ break;
+ default:
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
* Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated
* to point to just after the separator. *start points to the first
* character of the matched text and *end just after the last
diff --git a/lib/libarchive/archive_read_support_format_tar.c b/lib/libarchive/archive_read_support_format_tar.c
index a2d08ea..57f420f 100644
--- a/lib/libarchive/archive_read_support_format_tar.c
+++ b/lib/libarchive/archive_read_support_format_tar.c
@@ -732,6 +732,7 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
const struct archive_entry_header_ustar *header;
size_t size;
int err;
+ int64_t type;
char *acl, *p;
wchar_t *wp;
@@ -744,24 +745,57 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
err = read_body_to_string(a, tar, &(tar->acl_text), h);
if (err != ARCHIVE_OK)
return (err);
+ /* Recursively read next header */
err = tar_read_header(a, tar, entry);
if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN))
return (err);
- /* Skip leading octal number. */
- /* XXX TODO: Parse the octal number and sanity-check it. */
+ /* TODO: Examine the first characters to see if this
+ * is an AIX ACL descriptor. We'll likely never support
+ * them, but it would be polite to recognize and warn when
+ * we do see them. */
+
+ /* Leading octal number indicates ACL type and number of entries. */
p = acl = tar->acl_text.s;
- while (*p != '\0' && p < acl + size)
+ type = 0;
+ while (*p != '\0' && p < acl + size) {
+ if (*p < '0' || *p > '7') {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Malformed Solaris ACL attribute (invalid digit)");
+ return(ARCHIVE_WARN);
+ }
+ type <<= 3;
+ type += *p - '0';
+ if (type > 077777777) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Malformed Solaris ACL attribute (count too large)");
+ return (ARCHIVE_WARN);
+ }
p++;
+ }
+ switch (type & ~0777777) {
+ case 01000000:
+ /* POSIX.1e ACL */
+ break;
+ case 03000000:
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Solaris NFSv4 ACLs not supported");
+ return (ARCHIVE_WARN);
+ default:
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Malformed Solaris ACL attribute (unsupported type %o)",
+ (int)type);
+ return (ARCHIVE_WARN);
+ }
p++;
if (p >= acl + size) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Malformed Solaris ACL attribute");
+ "Malformed Solaris ACL attribute (body overflow)");
return(ARCHIVE_WARN);
}
- /* Skip leading octal number. */
+ /* ACL text is null-terminated; find the end. */
size -= (p - acl);
acl = p;
@@ -771,6 +805,9 @@ header_Solaris_ACL(struct archive_read *a, struct tar *tar,
wp = utf8_decode(tar, acl, p - acl);
err = __archive_entry_acl_parse_w(entry, wp,
ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ if (err != ARCHIVE_OK)
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Malformed Solaris ACL attribute (unparsable)");
return (err);
}
diff --git a/lib/libarchive/test/Makefile b/lib/libarchive/test/Makefile
index d266d82..b6aeff1 100644
--- a/lib/libarchive/test/Makefile
+++ b/lib/libarchive/test/Makefile
@@ -16,6 +16,7 @@ TESTS= \
test_compat_bzip2.c \
test_compat_gtar.c \
test_compat_gzip.c \
+ test_compat_solaris_tar_acl.c \
test_compat_tar_hardlink.c \
test_compat_xz.c \
test_compat_zip.c \
diff --git a/lib/libarchive/test/test_acl_pax.c b/lib/libarchive/test/test_acl_pax.c
index 4a37199..5898a66 100644
--- a/lib/libarchive/test/test_acl_pax.c
+++ b/lib/libarchive/test/test_acl_pax.c
@@ -151,10 +151,10 @@ static unsigned char reference[] = {
0,0,0,0,0,0,0,0,0,0,'1','1','3',' ','S','C','H','I','L','Y','.','a','c','l',
'.','a','c','c','e','s','s','=','u','s','e','r',':',':','r','-','x',',','g',
'r','o','u','p',':',':','r','-','-',',','o','t','h','e','r',':',':','-','w',
-'x',',','g','r','o','u','p',':','g','r','o','u','p','7','8',':','r','w','x',
-':','7','8',',','u','s','e','r',':','u','s','e','r','7','8',':','-','-','-',
-':','7','8',',','u','s','e','r',':','u','s','e','r','7','7',':','r','-','-',
-':','7','7',10,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=','0',
+'x',',','u','s','e','r',':','u','s','e','r','7','7',':','r','-','-',':','7',
+'7',',','u','s','e','r',':','u','s','e','r','7','8',':','-','-','-',':','7',
+'8',',','g','r','o','u','p',':','g','r','o','u','p','7','8',':','r','w','x',
+':','7','8',10,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=','0',
10,'1','6',' ','S','C','H','I','L','Y','.','i','n','o','=','0',10,'1','8',
' ','S','C','H','I','L','Y','.','n','l','i','n','k','=','0',10,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -464,7 +464,7 @@ DEFINE_TEST(test_acl_pax)
/* Assert that the generated data matches the built-in reference data.*/
failure("Generated pax archive does not match reference; check 'testout' and 'reference' files.");
- assert(0 == memcmp(buff, reference, sizeof(reference)));
+ assertEqualMem(buff, reference, sizeof(reference));
failure("Generated pax archive does not match reference; check 'testout' and 'reference' files.");
assertEqualInt((int)used, sizeof(reference));
diff --git a/lib/libarchive/test/test_compat_solaris_tar_acl.c b/lib/libarchive/test/test_compat_solaris_tar_acl.c
new file mode 100644
index 0000000..ec3955e
--- /dev/null
+++ b/lib/libarchive/test/test_compat_solaris_tar_acl.c
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 2003-2009 Tim Kientzle
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+/*
+ * Exercise support for reading Solaris-style ACL data
+ * from tar archives.
+ *
+ * This should work on all systems, regardless of whether local
+ * filesystems support ACLs or not.
+ */
+
+DEFINE_TEST(test_compat_solaris_tar_acl)
+{
+ struct archive *a;
+ struct archive_entry *ae;
+ const char *reference1 = "test_compat_solaris_tar_acl.tar";
+ int type, permset, tag, qual;
+ const char *name;
+
+ /* Sample file generated on Solaris 10 */
+ extract_reference_file(reference1);
+ assert(NULL != (a = archive_read_new()));
+ assertA(0 == archive_read_support_format_all(a));
+ assertA(0 == archive_read_support_compression_all(a));
+ assertA(0 == archive_read_open_filename(a, reference1, 512));
+
+ /* Archive has 1 entry with some ACLs set on it. */
+ assertA(0 == archive_read_next_header(a, &ae));
+ failure("Basic ACLs should set mode to 0640, not %04o",
+ archive_entry_mode(ae)&0777);
+ assertEqualInt((archive_entry_mode(ae) & 0777), 0640);
+ assertEqualInt(7, archive_entry_acl_reset(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS));
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(006, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_USER_OBJ, tag);
+ assertEqualInt(-1, qual);
+ assert(name == NULL);
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(004, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_GROUP_OBJ, tag);
+ assertEqualInt(-1, qual);
+ assert(name == NULL);
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(000, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_OTHER, tag);
+ assertEqualInt(-1, qual);
+ assert(name == NULL);
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(001, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
+ assertEqualInt(71, qual);
+ assertEqualString(name, "lp");
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(004, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
+ assertEqualInt(666, qual);
+ assertEqualString(name, "666");
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(007, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_USER, tag);
+ assertEqualInt(1000, qual);
+ assertEqualString(name, "trasz");
+
+ assertEqualInt(ARCHIVE_OK, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+ assertEqualInt(ARCHIVE_ENTRY_ACL_TYPE_ACCESS, type);
+ assertEqualInt(004, permset);
+ assertEqualInt(ARCHIVE_ENTRY_ACL_MASK, tag);
+ assertEqualInt(-1, qual);
+ assertEqualString(name, NULL);
+
+ assertEqualInt(ARCHIVE_EOF, archive_entry_acl_next(ae,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS,
+ &type, &permset, &tag, &qual, &name));
+
+ /* Close the archive. */
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+ assertEqualInt(ARCHIVE_OK, archive_read_finish(a));
+}
diff --git a/lib/libarchive/test/test_compat_solaris_tar_acl.tar.uu b/lib/libarchive/test/test_compat_solaris_tar_acl.tar.uu
new file mode 100644
index 0000000..229b335
--- /dev/null
+++ b/lib/libarchive/test/test_compat_solaris_tar_acl.tar.uu
@@ -0,0 +1,61 @@
+$FreeBSD$
+begin 644 test_acl_solaris.tar
+M9FEL92UW:71H+7!O<VEX+6%C;',`````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#`V-#0`,#`P,3<U,``P,#`P,#`P`#`P,#`P,#`P,30T
+M`#$Q,3<T-C`T,34W`#`P,34Q-S8`00``````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,'1R87-Z
+M````````````````````````````````````<F]O=```````````````````
+M```````````````````P,#`P,C$P`#`P,#`P,3``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````Q,#`P,#`W`'5S97(Z.G)W+2QU<V5R.FQP.BTM
+M>#HW,2QU<V5R.C8V-CIR+2TZ-C8V+'5S97(Z=')A<WHZ<G=X.C$P,#`L9W)O
+M=7`Z.G(M+2QM87-K.G(M+2QO=&AE<CIR+2T``````````3````````/-@```
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````!%&8`````````&L`````,3`P,#`P-P!U
+M<V5R.CIR=RTL=7-E<CIL<#HM+7@Z-S$L=7-E<CHV-C8Z<BTM.C8V-BQU<V5R
+M.G1R87-Z.G)W>#HQ,#`P+&=R;W5P.CIR+2TL;6%S:SIR+69I;&4M=VET:"UP
+M;W-I>"UA8VQS````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`P-C0T`#`P,#$W-3``,#`P,#`P,``P,#`P,#`P,#`P,``Q,3$W-#8P-#$U
+M-P`P,#$U,30T`#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#!T<F%S>@``````````````
+M`````````````````````')O;W0`````````````````````````````````
+M````,#`P,#(Q,``P,#`P,#$P````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+H````````````````````````````````````````````````````````
+`
+end
OpenPOWER on IntegriCloud