prepstrip: preserve xattrs, bug #446420
authorZac Medico <zmedico@gentoo.org>
Tue, 11 Dec 2012 09:00:25 +0000 (01:00 -0800)
committerZac Medico <zmedico@gentoo.org>
Tue, 11 Dec 2012 09:01:03 +0000 (01:01 -0800)
bin/ebuild-helpers/prepstrip
bin/xattr-helper.py [new file with mode: 0755]

index 6a09ff4570308af0ad12d57238ad2ccebe60241b..fb20777e886af12ccb2942254c2599e4ec6b6d1f 100755 (executable)
@@ -15,7 +15,7 @@ exp_tf() {
                eval ${var}_${flag}=$(tf has ${flag} ${!var})
        done
 }
-exp_tf FEATURES compressdebug installsources nostrip splitdebug
+exp_tf FEATURES compressdebug installsources nostrip splitdebug xattr
 exp_tf RESTRICT binchecks installsources strip
 
 if ! ___eapi_has_prefix_variables; then
@@ -30,6 +30,28 @@ if ${RESTRICT_strip} || ${FEATURES_nostrip} ; then
        ${FEATURES_installsources} || exit 0
 fi
 
+PRESERVE_XATTR=false
+if [[ ${KERNEL} == linux ]] && ${FEATURES_xattr} ; then
+       PRESERVE_XATTR=true
+       if type -P getfattr >/dev/null && type -P setfattr >/dev/null ; then
+               dump_xattrs() {
+                       getfattr -d --absolute-names "$1"
+               }
+               restore_xattrs() {
+                       setfattr --restore=-
+               }
+       else
+               dump_xattrs() {
+                       "${PORTAGE_PYTHON:-/usr/bin/python}" \
+                       "${PORTAGE_BIN_PATH}/xattr-helper.py" --dump < <(echo -n "$1")
+               }
+               restore_xattrs() {
+                       "${PORTAGE_PYTHON:-/usr/bin/python}" \
+                       "${PORTAGE_BIN_PATH}/xattr-helper.py" --restore
+               }
+       fi
+fi
+
 # look up the tools we might be using
 for t in STRIP:strip OBJCOPY:objcopy READELF:readelf ; do
        v=${t%:*} # STRIP
@@ -152,7 +174,7 @@ save_elf_debug() {
 # Usage: process_elf <elf>
 process_elf() {
        local x=$1 inode_link=$2 strip_flags=${*:3}
-       local already_stripped lockfile
+       local already_stripped lockfile xt_data
 
        __vecho "   ${x:${#ED}}"
 
@@ -171,7 +193,12 @@ process_elf() {
 
        [ -f "${inode_link}_stripped" ] && already_stripped=true || already_stripped=false
 
-       ${already_stripped} || save_elf_sources "${x}"
+       if ! ${already_stripped} ; then
+               if ${PRESERVE_XATTR} ; then
+                       xt_data=$(dump_xattrs "${x}")
+               fi
+               save_elf_sources "${x}"
+       fi
 
        if ${strip_this} ; then
 
@@ -197,6 +224,9 @@ process_elf() {
                ln "${inode_link}_stripped" "${x}" || die "ln failed unexpectedly"
        else
                ln "${x}" "${inode_link}_stripped" || die "ln failed unexpectedly"
+               if [[ ${xt_data} ]] ; then
+                       restore_xattrs <<< "${xt_data}"
+               fi
        fi
 
        [[ -n ${lockfile} ]] && rm -f "${lockfile}"
diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py
new file mode 100755 (executable)
index 0000000..d40217c
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+# Copyright 2012 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import array
+import optparse
+import os
+import re
+import sys
+
+if hasattr(os, "getxattr"):
+
+       class xattr(object):
+               get = os.getxattr
+               set = os.setxattr
+               list = os.listxattr
+
+else:
+       import xattr
+
+_unquote_re = re.compile(br'\\[0-7]{3}')
+_fs_encoding = sys.getfilesystemencoding()
+
+if sys.hexversion < 0x3000000:
+
+       def octal_quote_byte(b):
+               return b'\\%03o' % ord(b)
+
+       def unicode_encode(s):
+               if isinstance(s, unicode):
+                       s = s.encode(_fs_encoding)
+               return s
+else:
+
+       def octal_quote_byte(b):
+               return ('\\%03o' % ord(b)).encode('ascii')
+
+       def unicode_encode(s):
+               if isinstance(s, str):
+                       s = s.encode(_fs_encoding)
+               return s
+
+def quote(s, quote_chars):
+
+       quote_re = re.compile(b'[' + quote_chars + b']')
+       result = []
+       pos = 0
+       s_len = len(s)
+
+       while pos < s_len:
+               m = quote_re.search(s, pos=pos)
+               if m is None:
+                       result.append(s[pos:])
+                       pos = s_len
+               else:
+                       start = m.start()
+                       result.append(s[pos:start])
+                       result.append(octal_quote_byte(s[start:start+1]))
+                       pos = start + 1
+
+       return b"".join(result)
+
+def unquote(s):
+
+       result = []
+       pos = 0
+       s_len = len(s)
+
+       while pos < s_len:
+               m = _unquote_re.search(s, pos=pos)
+               if m is None:
+                       result.append(s[pos:])
+                       pos = s_len
+               else:
+                       start = m.start()
+                       result.append(s[pos:start])
+                       pos = start + 4
+                       a = array.array('B')
+                       a.append(int(s[start + 1:pos], 8))
+                       try:
+                               # Python >= 3.2
+                               result.append(a.tobytes())
+                       except AttributeError:
+                               result.append(a.tostring())
+
+       return b"".join(result)
+
+def dump_xattrs(file_in, file_out):
+
+       for pathname in file_in.read().split(b'\0'):
+               if not pathname:
+                       continue
+
+               attrs = xattr.list(pathname)
+               if not attrs:
+                       continue
+               file_out.write(b'# file: ' + quote(pathname, b'\n\r') + b'\n')
+               for attr in attrs:
+                       attr = unicode_encode(attr)
+                       file_out.write(quote(attr, b'=\n\r') + b'="' +
+                               quote(xattr.get(pathname, attr), b'\\\0\n\r"') + b'"\n')
+
+def restore_xattrs(file_in):
+
+       pathname = None
+       for i, line in enumerate(file_in):
+               if line.startswith(b'# file: '):
+                       pathname = unquote(line.rstrip(b'\n')[8:])
+               else:
+                       parts = line.split(b'=', 1)
+                       if len(parts) == 2:
+                               if pathname is None:
+                                       raise AssertionError('line %d: missing pathname' % i + 1)
+                               attr = unquote(parts[0])
+                               # strip trailing newline and quotes 
+                               value = unquote(parts[1].rstrip(b'\n')[1:-1])
+                               xattr.set(pathname, attr, value)
+                       elif line.strip():
+                               raise AssertionError("line %d: malformed entry" % i + 1)
+
+def main(argv):
+
+       description = "Dump and restore extended attributes," \
+               " using format like that used by getfattr --dump."
+       usage = "usage: %s [--dump | --restore]\n" % \
+               os.path.basename(argv[0])
+
+       parser = optparse.OptionParser(description=description, usage=usage)
+
+       actions = optparse.OptionGroup(parser, 'Actions')
+       actions.add_option("--dump",
+               action="store_true",
+               help="Dump the values of all extended "
+                       "attributes associated with null-separated"
+                       " paths read from stdin.")
+       actions.add_option("--restore",
+               action="store_true",
+               help="Restore extended attributes using"
+                       " a dump read from stdin.")
+       parser.add_option_group(actions)
+
+       options, args = parser.parse_args(argv[1:])
+
+       if len(args) != 0:
+               parser.error("expected zero arguments, "
+                       "got %s" % len(args))
+
+       if sys.hexversion >= 0x3000000:
+               file_in = sys.stdin.buffer.raw
+       else:
+               file_in = sys.stdin
+
+       if options.dump:
+
+               if sys.hexversion >= 0x3000000:
+                       file_out = sys.stdout.buffer
+               else:
+                       file_out = sys.stdout
+
+               dump_xattrs(file_in, file_out)
+
+       elif options.restore:
+
+               restore_xattrs(file_in)
+
+       else:
+               parser.error("available actions: --dump, --restore")
+
+       return os.EX_OK
+
+if __name__ == "__main__":
+       rval = main(sys.argv[:])
+       sys.exit(rval)