libtar: backup and restore posix capabilities

This patch also allows libtar to combine data from multiple extended
tar headers into a single header.

Change-Id: I82d13e89a3622ea665b60062b1904ddbedfa41b3
diff --git a/libtar/append.c b/libtar/append.c
index 4388297..7c679f6 100644
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -22,6 +22,10 @@
 #include <sys/types.h>
 #include <stdbool.h>
 
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
 #ifdef STDC_HEADERS
 # include <stdlib.h>
 # include <string.h>
@@ -154,6 +158,24 @@
 	}
 #endif
 
+	/* get posix file capabilities */
+	if (TH_ISREG(t) && t->options & TAR_STORE_POSIX_CAP)
+	{
+		if (t->th_buf.has_cap_data)
+		{
+			memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+			t->th_buf.has_cap_data = 0;
+		}
+
+		if (getxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data)) >= 0)
+		{
+			t->th_buf.has_cap_data = 1;
+#if 1 //def DEBUG
+			print_caps(&t->th_buf.cap_data);
+#endif
+		}
+	}
+
 	/* check if it's a hardlink */
 #ifdef DEBUG
 	puts("tar_append_file(): checking inode cache for hardlink...");
diff --git a/libtar/block.c b/libtar/block.c
index 2fd61bb..a117857 100644
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -29,6 +29,10 @@
 #define E4CRYPT_TAG "TWRP.security.e4crypt="
 #define E4CRYPT_TAG_LEN 22
 
+// Used to identify Posix capabilities in extended ('x')
+#define CAPABILITIES_TAG "SCHILY.xattr.security.capability="
+#define CAPABILITIES_TAG_LEN 33
+
 /* read a header block */
 /* FIXME: the return value of this function should match the return value
 	  of tar_block_read(), which is a macro which references a prototype
@@ -128,6 +132,11 @@
 		free(t->th_buf.e4crypt_policy);
 	}
 #endif
+	if (t->th_buf.has_cap_data)
+	{
+		memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+		t->th_buf.has_cap_data = 0;
+	}
 
 	memset(&(t->th_buf), 0, sizeof(struct tar_header));
 
@@ -241,8 +250,8 @@
 		}
 	}
 
-#ifdef HAVE_SELINUX
-	if(TH_ISEXTHEADER(t))
+	// Extended headers (selinux contexts, posix file capabilities, ext4 encryption policies)
+	while(TH_ISEXTHEADER(t) || TH_ISPOLHEADER(t))
 	{
 		sz = th_get_size(t);
 
@@ -267,7 +276,19 @@
 			buf[T_BLOCKSIZE-1] = 0;
 
 			int len = strlen(buf);
-			char *start = strstr(buf, SELINUX_TAG);
+			// posix capabilities
+			char *start = strstr(buf, CAPABILITIES_TAG);
+			if(start && start+CAPABILITIES_TAG_LEN < buf+len)
+			{
+				start += CAPABILITIES_TAG_LEN;
+				memcpy(&t->th_buf.cap_data, start, sizeof(struct vfs_cap_data));
+				t->th_buf.has_cap_data = 1;
+#ifdef DEBUG
+				printf("    th_read(): Posix capabilities detected\n");
+#endif
+			} // end posix capabilities
+#ifdef HAVE_SELINUX // selinux contexts
+			start = strstr(buf, SELINUX_TAG);
 			if(start && start+SELINUX_TAG_LEN < buf+len)
 			{
 				start += SELINUX_TAG_LEN;
@@ -280,45 +301,9 @@
 #endif
 				}
 			}
-		}
-
-		i = th_read_internal(t);
-		if (i != T_BLOCKSIZE)
-		{
-			if (i != -1)
-				errno = EINVAL;
-			return -1;
-		}
-	}
-#endif
-
+#endif // HAVE_SELINUX
 #ifdef HAVE_EXT4_CRYPT
-	if(TH_ISPOLHEADER(t))
-	{
-		sz = th_get_size(t);
-
-		if(sz >= T_BLOCKSIZE) // Not supported
-		{
-#ifdef DEBUG
-			printf("    th_read(): Policy header is too long!\n");
-#endif
-		}
-		else
-		{
-			char buf[T_BLOCKSIZE];
-			i = tar_block_read(t, buf);
-			if (i != T_BLOCKSIZE)
-			{
-				if (i != -1)
-					errno = EINVAL;
-				return -1;
-			}
-
-			// To be sure
-			buf[T_BLOCKSIZE-1] = 0;
-
-			int len = strlen(buf);
-			char *start = strstr(buf, E4CRYPT_TAG);
+			start = strstr(buf, E4CRYPT_TAG);
 			if(start && start+E4CRYPT_TAG_LEN < buf+len)
 			{
 				start += E4CRYPT_TAG_LEN;
@@ -331,6 +316,7 @@
 #endif
 				}
 			}
+#endif // HAVE_EXT4_CRYPT
 		}
 
 		i = th_read_internal(t);
@@ -341,11 +327,55 @@
 			return -1;
 		}
 	}
-#endif
 
 	return 0;
 }
 
+/* write an extended block */
+static int
+th_write_extended(TAR *t, char* buf, uint64_t sz)
+{
+	char type2;
+	uint64_t sz2;
+	int i;
+
+	/* save old size and type */
+	type2 = t->th_buf.typeflag;
+	sz2 = th_get_size(t);
+
+	/* write out initial header block with fake size and type */
+	t->th_buf.typeflag = TH_EXT_TYPE;
+
+	if(sz >= T_BLOCKSIZE) // impossible
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	th_set_size(t, sz);
+	th_finish(t);
+	i = tar_block_write(t, &(t->th_buf));
+	if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+	i = tar_block_write(t, buf);
+	if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+	/* reset type and size to original values */
+	t->th_buf.typeflag = type2;
+	th_set_size(t, sz2);
+	memset(buf, 0, T_BLOCKSIZE);
+	return 0;
+}
 
 /* write a header block */
 int
@@ -353,7 +383,7 @@
 {
 	int i, j;
 	char type2;
-	uint64_t sz, sz2;
+	uint64_t sz, sz2, total_sz = 0;
 	char *ptr;
 	char buf[T_BLOCKSIZE];
 
@@ -464,6 +494,8 @@
 		th_set_size(t, sz2);
 	}
 
+	memset(buf, 0, T_BLOCKSIZE);
+	ptr = buf;
 #ifdef HAVE_SELINUX
 	if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
 	{
@@ -471,13 +503,6 @@
 		printf("th_write(): using selinux_context (\"%s\")\n",
 		       t->th_buf.selinux_context);
 #endif
-		/* save old size and type */
-		type2 = t->th_buf.typeflag;
-		sz2 = th_get_size(t);
-
-		/* write out initial header block with fake size and type */
-		t->th_buf.typeflag = TH_EXT_TYPE;
-
 		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
 		//                                                       size   newline
 		sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 3  +    1;
@@ -485,35 +510,9 @@
 		if(sz >= 100) // another ascci digit for size
 			++sz;
 
-		if(sz >= T_BLOCKSIZE) // impossible
-		{
-			errno = EINVAL;
-			return -1;
-		}
-
-		th_set_size(t, sz);
-		th_finish(t);
-		i = tar_block_write(t, &(t->th_buf));
-		if (i != T_BLOCKSIZE)
-		{
-			if (i != -1)
-				errno = EINVAL;
-			return -1;
-		}
-
-		memset(buf, 0, T_BLOCKSIZE);
-		snprintf(buf, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", (int)sz, t->th_buf.selinux_context);
-		i = tar_block_write(t, &buf);
-		if (i != T_BLOCKSIZE)
-		{
-			if (i != -1)
-				errno = EINVAL;
-			return -1;
-		}
-
-		/* reset type and size to original values */
-		t->th_buf.typeflag = type2;
-		th_set_size(t, sz2);
+		total_sz += sz;
+		snprintf(ptr, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", (int)sz, t->th_buf.selinux_context);
+		ptr += sz;
 	}
 #endif
 
@@ -524,13 +523,6 @@
 		printf("th_write(): using e4crypt_policy %s\n",
 		       t->th_buf.e4crypt_policy);
 #endif
-		/* save old size and type */
-		type2 = t->th_buf.typeflag;
-		sz2 = th_get_size(t);
-
-		/* write out initial header block with fake size and type */
-		t->th_buf.typeflag = TH_POL_TYPE;
-
 		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
 		//                                                       size   newline
 		sz = E4CRYPT_TAG_LEN + EXT4_KEY_DESCRIPTOR_HEX + 3  +    1;
@@ -538,38 +530,52 @@
 		if(sz >= 100) // another ascci digit for size
 			++sz;
 
-		if(sz >= T_BLOCKSIZE) // impossible
+		if (total_sz + sz >= T_BLOCKSIZE)
 		{
-			errno = EINVAL;
-			return -1;
+			if (th_write_extended(t, &buf, total_sz))
+				return -1;
+			ptr = buf;
+			total_sz = sz;
 		}
+		else
+			total_sz += sz;
 
-		th_set_size(t, sz);
-		th_finish(t);
-		i = tar_block_write(t, &(t->th_buf));
-		if (i != T_BLOCKSIZE)
-		{
-			if (i != -1)
-				errno = EINVAL;
-			return -1;
-		}
-
-		memset(buf, 0, T_BLOCKSIZE);
-		snprintf(buf, T_BLOCKSIZE, "%d "E4CRYPT_TAG"%s\n", (int)sz, t->th_buf.e4crypt_policy);
-		i = tar_block_write(t, &buf);
-		if (i != T_BLOCKSIZE)
-		{
-			if (i != -1)
-				errno = EINVAL;
-			return -1;
-		}
-
-		/* reset type and size to original values */
-		t->th_buf.typeflag = type2;
-		th_set_size(t, sz2);
+		snprintf(ptr, T_BLOCKSIZE, "%d "E4CRYPT_TAG"%s", (int)sz, t->th_buf.e4crypt_policy);
+		char *nlptr = ptr + sz - 1;
+		*nlptr = '\n';
+		ptr += sz;
 	}
 #endif
 
+	if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+	{
+#ifdef DEBUG
+		printf("th_write(): has a posix capability\n");
+#endif
+		sz = CAPABILITIES_TAG_LEN + sizeof(struct vfs_cap_data) + 3 + 1;
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		if (total_sz + sz >= T_BLOCKSIZE)
+		{
+			if (th_write_extended(t, &buf, total_sz))
+				return -1;
+			ptr = buf;
+			total_sz = sz;
+		}
+		else
+			total_sz += sz;
+
+		snprintf(ptr, T_BLOCKSIZE, "%d "CAPABILITIES_TAG, (int)sz);
+		memcpy(ptr + CAPABILITIES_TAG_LEN + 3, &t->th_buf.cap_data, sizeof(struct vfs_cap_data));
+		char *nlptr = ptr + sz - 1;
+		*nlptr = '\n';
+		ptr += sz;
+	}
+	if (total_sz > 0 && th_write_extended(t, &buf, total_sz)) // write any outstanding tar extended header
+		return -1;
+
 	th_finish(t);
 
 #ifdef DEBUG
diff --git a/libtar/extract.c b/libtar/extract.c
index ba29a77..9e24e8e 100644
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -20,6 +20,10 @@
 #include <errno.h>
 #include <utime.h>
 
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
 #ifdef STDC_HEADERS
 # include <stdlib.h>
 #endif
@@ -166,6 +170,16 @@
 	}
 #endif
 
+	if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+	{
+#if 1 //def DEBUG
+		printf("tar_extract_file(): restoring posix capabilities to file %s\n", realname);
+		print_caps(&t->th_buf.cap_data);
+#endif
+		if (setxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data), 0) < 0)
+			fprintf(stderr, "tar_extract_file(): failed to restore posix capabilities to file %s !!!\n", realname);
+	}
+
 #ifdef LIBTAR_FILE_HASH
 	pn = th_get_pathname(t);
 	pathname_len = strlen(pn) + 1;
diff --git a/libtar/libtar.h b/libtar/libtar.h
index ab5a3be..8c42028 100644
--- a/libtar/libtar.h
+++ b/libtar/libtar.h
@@ -15,6 +15,7 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <linux/capability.h>
 #include "tar.h"
 
 #include "libtar_listhash.h"
@@ -43,7 +44,7 @@
 
 /* extended metadata for next file - used to store selinux_context */
 #define TH_EXT_TYPE		'x'
-#define TH_POL_TYPE		'p'
+#define TH_POL_TYPE_DO_NOT_USE		'p'
 
 /* our version of the tar header structure */
 struct tar_header
@@ -73,6 +74,8 @@
 #ifdef HAVE_EXT4_CRYPT
 	char *e4crypt_policy;
 #endif
+	int has_cap_data;
+	struct vfs_cap_data cap_data;
 };
 
 
@@ -118,6 +121,7 @@
 #define TAR_STORE_SELINUX	128	/* store selinux context */
 #define TAR_USE_NUMERIC_ID	256	/* favor numeric owner over names */
 #define TAR_STORE_EXT4_POL	512	/* store ext4 crypto policy */
+#define TAR_STORE_POSIX_CAP	1024	/* store posix file capabilities */
 
 /* this is obsolete - it's here for backwards-compatibility only */
 #define TAR_IGNORE_MAGIC	0
@@ -214,7 +218,7 @@
 #define TH_ISLONGNAME(t)	((t)->th_buf.typeflag == GNU_LONGNAME_TYPE)
 #define TH_ISLONGLINK(t)	((t)->th_buf.typeflag == GNU_LONGLINK_TYPE)
 #define TH_ISEXTHEADER(t)	((t)->th_buf.typeflag == TH_EXT_TYPE)
-#define TH_ISPOLHEADER(t)	((t)->th_buf.typeflag == TH_POL_TYPE)
+#define TH_ISPOLHEADER(t)	((t)->th_buf.typeflag == TH_POL_TYPE_DO_NOT_USE)
 
 /* decode tar header info */
 #define th_get_crc(t) oct_to_int((t)->th_buf.chksum, sizeof((t)->th_buf.chksum))
@@ -323,6 +327,9 @@
 /* integer to string-octal conversion, or binary as necessary */
 void int_to_oct_ex(int64_t num, char *oct, size_t octlen);
 
+/* prints posix file capabilities */
+void print_caps(struct vfs_cap_data *cap_data);
+
 
 /***** wrapper.c **********************************************************/
 
diff --git a/libtar/util.c b/libtar/util.c
index f472f38..7fb3f51 100644
--- a/libtar/util.c
+++ b/libtar/util.c
@@ -15,6 +15,7 @@
 #include <stdio.h>
 #include <sys/param.h>
 #include <errno.h>
+#include <linux/capability.h>
 
 #ifdef STDC_HEADERS
 # include <string.h>
@@ -210,3 +211,11 @@
 	}
 	int_to_oct(num, oct, octlen);
 }
+
+void print_caps(struct vfs_cap_data *cap_data) {
+	printf("     magic_etc=%u \n", cap_data->magic_etc);
+	printf("     data[0].permitted=%u \n", cap_data->data[0].permitted);
+	printf("     data[0].inheritable=%u \n", cap_data->data[0].inheritable);
+	printf("     data[1].permitted=%u \n", cap_data->data[1].permitted);
+	printf("     data[1].inheritable=%u \n", cap_data->data[1].inheritable);
+}
diff --git a/twrpTar.cpp b/twrpTar.cpp
index 4f9b633..27c7c7b 100644
--- a/twrpTar.cpp
+++ b/twrpTar.cpp
@@ -55,9 +55,9 @@
 
 #ifdef TW_INCLUDE_FBE
 #include "crypto/ext4crypt/ext4crypt_tar.h"
-#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_EXT4_POL
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP |TAR_STORE_EXT4_POL
 #else
-#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP
 #endif
 
 using namespace std;