]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add the "xattrhdr" option to pax write options. 1288/head
authorMartin Matuska <martin@matuska.org>
Tue, 3 Dec 2019 00:09:42 +0000 (01:09 +0100)
committerMartin Matuska <martin@matuska.org>
Wed, 11 Dec 2019 11:04:31 +0000 (12:04 +0100)
This allows us to control whether "SCHILY.xattr", "LIBARCHIVE.xattr" or
both headers (default) are written when storing extended attributes.

Document "hdrcharset" option for pax.

Makefile.am
libarchive/archive_write_set_format_pax.c
libarchive/archive_write_set_options.3
libarchive/test/CMakeLists.txt
libarchive/test/test_pax_xattr_header.c [new file with mode: 0644]
libarchive/test/test_pax_xattr_header_all.tar.uu [new file with mode: 0644]
libarchive/test/test_pax_xattr_header_libarchive.tar.uu [new file with mode: 0644]
libarchive/test/test_pax_xattr_header_schily.tar.uu [new file with mode: 0644]

index 6816eb53283c9740f3dbe5171ee96a5329fca5c8..6d864fb66fa194c7361c2b6b1b250600b7d82957 100644 (file)
@@ -429,6 +429,7 @@ libarchive_test_SOURCES= \
        libarchive/test/test_open_file.c \
        libarchive/test/test_open_filename.c \
        libarchive/test/test_pax_filename_encoding.c \
+       libarchive/test/test_pax_xattr_header.c \
        libarchive/test/test_read_data_large.c \
        libarchive/test/test_read_disk.c \
        libarchive/test/test_read_disk_directory_traversals.c \
@@ -702,6 +703,9 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_fuzz.lzh.uu \
        libarchive/test/test_fuzz_1.iso.Z.uu \
        libarchive/test/test_pax_filename_encoding.tar.uu \
+       libarchive/test/test_pax_xattr_header_all.tar.uu \
+       libarchive/test/test_pax_xattr_header_libarchive.tar.uu \
+       libarchive/test/test_pax_xattr_header_schily.tar.uu \
        libarchive/test/test_rar_multivolume_multiple_files.part1.rar.uu \
        libarchive/test/test_rar_multivolume_multiple_files.part2.rar.uu \
        libarchive/test/test_rar_multivolume_multiple_files.part3.rar.uu \
index cf2a1f959e341a384cb03d59bab70411537a944d..7c5e63bb3a233e1e71d66f61fa3c33a22125398e 100644 (file)
@@ -199,6 +199,28 @@ archive_write_pax_options(struct archive_write *a, const char *key,
                        archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
                            "pax: invalid charset name");
                return (ret);
+       } else if (strcmp(key, "xattrheader") == 0) {
+               if (val == NULL || val[0] == 0) {
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "pax: xattrheader requires a value");
+               } else if (strcmp(val, "ALL") == 0 ||
+                   strcmp(val, "all") == 0) {
+                       pax->flags |= WRITE_LIBARCHIVE_XATTR | WRITE_SCHILY_XATTR;
+                       ret = ARCHIVE_OK;
+               } else if (strcmp(val, "SCHILY") == 0 ||
+                   strcmp(val, "schily") == 0) {
+                       pax->flags |= WRITE_SCHILY_XATTR;
+                       pax->flags &= ~WRITE_LIBARCHIVE_XATTR;
+                       ret = ARCHIVE_OK;
+               } else if (strcmp(val, "LIBARCHIVE") == 0 ||
+                   strcmp(val, "libarchive") == 0) {
+                       pax->flags |= WRITE_LIBARCHIVE_XATTR;
+                       pax->flags &= ~WRITE_SCHILY_XATTR;
+                       ret = ARCHIVE_OK;
+               } else
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "pax: invalid xattr header name");
+               return (ret);
        }
 
        /* Note: The "warn" return is just to inform the options
index a9f70a664092bd44430d3b6e586d2c346ab1279d..09eb95ea5aa9a11f49aa023f57766277795e99a0 100644 (file)
@@ -24,7 +24,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd July 27, 2019
+.Dd December 3, 2019
 .Dt ARCHIVE_WRITE_OPTIONS 3
 .Os
 .Sh NAME
@@ -404,6 +404,32 @@ Specifies a filename that should not be compressed when using
 This option can be provided multiple times to suppress compression
 on many files.
 .El
+.It Format pax
+.Bl -tag -compact -width indent
+.It Cm hdrcharset
+This sets the character set used for filenames, uname and gname.
+The value is one of
+.Dq BINARY
+or
+.Dq UTF-8 .
+With
+.Dq BINARY
+there is no character conversion, with
+.Dq UTF-8
+names are converted to UTF-8.
+.It Cm xattrheader
+When storing extended attributes, this option configures which
+headers should be written. The value is one of
+.Dq all ,
+.Dq LIBARCHIVE ,
+or
+.Dq SCHILY .
+By default, both
+.Dq LIBARCHIVE.xattr
+and
+.Dq SCHILY.xattr
+headers are written.
+.El
 .It Format 7zip
 .Bl -tag -compact -width indent
 .It Cm compression
index b25892565bd785603d783f3109ccdd4f0c7bd832..0822e591d13d3a6c32312f10da8f26079fd28f0d 100644 (file)
@@ -82,6 +82,7 @@ IF(ENABLE_TEST)
     test_open_file.c
     test_open_filename.c
     test_pax_filename_encoding.c
+    test_pax_xattr_header.c
     test_read_data_large.c
     test_read_disk.c
     test_read_disk_directory_traversals.c
diff --git a/libarchive/test/test_pax_xattr_header.c b/libarchive/test/test_pax_xattr_header.c
new file mode 100644 (file)
index 0000000..d0394aa
--- /dev/null
@@ -0,0 +1,130 @@
+/*-
+ * Copyright (c) 2019 Martin Matuska
+ * 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$");
+
+static struct archive_entry*
+create_archive_entry(void) {
+       struct archive_entry *ae;
+
+       assert((ae = archive_entry_new()) != NULL);
+        archive_entry_set_atime(ae, 2, 20);
+        archive_entry_set_ctime(ae, 4, 40);
+        archive_entry_set_mtime(ae, 5, 50);
+        archive_entry_copy_pathname(ae, "file");
+        archive_entry_set_mode(ae, AE_IFREG | 0755);
+        archive_entry_set_nlink(ae, 2);
+        archive_entry_set_size(ae, 8);
+        archive_entry_xattr_add_entry(ae, "user.data1", "ABCDEFG", 7);
+        archive_entry_xattr_add_entry(ae, "user.data2", "XYZ", 3);
+
+       return (ae);
+}
+
+DEFINE_TEST(test_pax_xattr_header)
+{
+       static const char *reffiles[] = {
+           "test_pax_xattr_header_all.tar",
+           "test_pax_xattr_header_libarchive.tar",
+           "test_pax_xattr_header_schily.tar",
+           NULL
+       };
+       struct archive *a;
+       struct archive_entry *ae;
+
+       extract_reference_files(reffiles);
+
+       /* First archive, no options */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, 0, archive_write_set_format_pax(a));
+       assertEqualIntA(a, 0, archive_write_add_filter_none(a));
+       assertEqualInt(0,
+           archive_write_open_filename(a, "test1.tar"));
+       ae = create_archive_entry();
+        assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+        archive_entry_free(ae);
+        assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+
+       assertEqualFile("test1.tar","test_pax_xattr_header_all.tar");
+
+       /* Second archive, xattrheader=SCHILY */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, 0, archive_write_set_format_pax(a));
+       assertEqualIntA(a, 0, archive_write_add_filter_none(a));
+       assertEqualIntA(a, 0, archive_write_set_options(a,
+           "xattrheader=SCHILY")); 
+       assertEqualInt(0,
+           archive_write_open_filename(a, "test2.tar"));
+
+       ae = create_archive_entry();
+        assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+        archive_entry_free(ae);
+        assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+
+       assertEqualFile("test2.tar","test_pax_xattr_header_schily.tar");
+
+       /* Third archive, xattrheader=LIBARCHIVE */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, 0, archive_write_set_format_pax(a));
+       assertEqualIntA(a, 0, archive_write_add_filter_none(a));
+       assertEqualIntA(a, 0, archive_write_set_options(a,
+           "xattrheader=LIBARCHIVE")); 
+       assertEqualInt(0,
+           archive_write_open_filename(a, "test3.tar"));
+
+       ae = create_archive_entry();
+        assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+        archive_entry_free(ae);
+        assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+
+       assertEqualFile("test3.tar","test_pax_xattr_header_libarchive.tar");
+
+       /* Fourth archive, xattrheader=ALL */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, 0, archive_write_set_format_pax(a));
+       assertEqualIntA(a, 0, archive_write_add_filter_none(a));
+       assertEqualIntA(a, 0, archive_write_set_options(a, "xattrheader=ALL")); 
+       assertEqualInt(0,
+           archive_write_open_filename(a, "test4.tar"));
+
+       ae = create_archive_entry();
+        assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+        archive_entry_free(ae);
+        assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+
+       assertEqualFile("test4.tar","test_pax_xattr_header_all.tar");
+}
diff --git a/libarchive/test/test_pax_xattr_header_all.tar.uu b/libarchive/test/test_pax_xattr_header_all.tar.uu
new file mode 100644 (file)
index 0000000..086428e
--- /dev/null
@@ -0,0 +1,72 @@
+begin 644 test_pax_xattr_header_all.tar
+M4&%X2&5A9&5R+V9I;&4`````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#<U-2``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,S$T
+M(#`P,#`P,#`P,#`U(#`Q,C`P-@`@>```````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,```````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````R,"!C=&EM93TT+C`P,#`P,#`T"C(P(&%T:6UE
+M/3(N,#`P,#`P,#(*,C`@;71I;64]-2XP,#`P,#`P-0HS-B!,24)!4D-(259%
+M+GAA='1R+G5S97(N9&%T83(]5T9L80HS,2!30TA)3%DN>&%T='(N=7-E<BYD
+M871A,CU865H*-#(@3$E"05)#2$E612YX871T<BYU<V5R+F1A=&$Q/5%52D12
+M159'4G<*,S4@4T-(24Q9+GAA='1R+G5S97(N9&%T83$]04)#1$5&1PH`````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````&9I;&4`````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`W-34@`#`P,#`P,"``,#`P,#`P(``P,#`P,#`P,#`Q,"`P,#`P,#`P,#`P
+M-2`P,3`P,C8`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#``````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````,3(S-#4V-S@`````````````````````````````````````````
+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````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+,````````````````
+`
+end
diff --git a/libarchive/test/test_pax_xattr_header_libarchive.tar.uu b/libarchive/test/test_pax_xattr_header_libarchive.tar.uu
new file mode 100644 (file)
index 0000000..1d15980
--- /dev/null
@@ -0,0 +1,72 @@
+begin 644 test_pax_xattr_header_libarchive.tar
+M4&%X2&5A9&5R+V9I;&4`````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#<U-2``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,C$R
+M(#`P,#`P,#`P,#`U(#`Q,C`P,P`@>```````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,```````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````R,"!C=&EM93TT+C`P,#`P,#`T"C(P(&%T:6UE
+M/3(N,#`P,#`P,#(*,C`@;71I;64]-2XP,#`P,#`P-0HS-B!,24)!4D-(259%
+M+GAA='1R+G5S97(N9&%T83(]5T9L80HT,B!,24)!4D-(259%+GAA='1R+G5S
+M97(N9&%T83$]455*1%)%5D=2=PH`````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````&9I;&4`````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`W-34@`#`P,#`P,"``,#`P,#`P(``P,#`P,#`P,#`Q,"`P,#`P,#`P,#`P
+M-2`P,3`P,C8`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#``````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````,3(S-#4V-S@`````````````````````````````````````````
+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````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+,````````````````
+`
+end
diff --git a/libarchive/test/test_pax_xattr_header_schily.tar.uu b/libarchive/test/test_pax_xattr_header_schily.tar.uu
new file mode 100644 (file)
index 0000000..aafea17
--- /dev/null
@@ -0,0 +1,72 @@
+begin 644 test_pax_xattr_header_schily.tar
+M4&%X2&5A9&5R+V9I;&4`````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````#`P,#<U-2``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,3<V
+M(#`P,#`P,#`P,#`U(#`Q,C`Q-``@>```````````````````````````````
+M````````````````````````````````````````````````````````````
+M``````````````````````````````````````````!U<W1A<@`P,```````
+M````````````````````````````````````````````````````````````
+M```````````````````P,#`P,#`@`#`P,#`P,"``````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M```````````````````````R,"!C=&EM93TT+C`P,#`P,#`T"C(P(&%T:6UE
+M/3(N,#`P,#`P,#(*,C`@;71I;64]-2XP,#`P,#`P-0HS,2!30TA)3%DN>&%T
+M='(N=7-E<BYD871A,CU865H*,S4@4T-(24Q9+GAA='1R+G5S97(N9&%T83$]
+M04)#1$5&1PH`````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M`````````````````````````````````````````````&9I;&4`````````
+M````````````````````````````````````````````````````````````
+M```````````````````````````````````````````````````````````P
+M,#`W-34@`#`P,#`P,"``,#`P,#`P(``P,#`P,#`P,#`Q,"`P,#`P,#`P,#`P
+M-2`P,3`P,C8`(#``````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````=7-T87(`,#``````````````````````
+M````````````````````````````````````````````````````````````
+M````,#`P,#`P(``P,#`P,#`@````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````,3(S-#4V-S@`````````````````````````````````````````
+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````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+,````````````````
+`
+end