commit 5765e4a16888e53ba7ee5a4e4127e94b91582d1d
Author: Tim Woodall <tim@woodall.me.uk>
Date:   Thu Sep 19 21:27:18 2024 +0100

    Correctly handle EXT4_INLINE_DATA_FL
    
    ext4 fs with -O inline_data (not the default on bookworm) would result
    in many corrupted small files as the data was not dumped correctly.
    Additionally the system.data EA will cause failures restoring other
    attributes on the file too.

--- a/restore/restore.h
+++ b/restore/restore.h
@@ -201,3 +201,7 @@ extern char	*transselinuxarg;
 	}
 
 #define XATTR_MAXSIZE	4096
+
+// 60 is the combined size of the direct and indirect block pointers in the inode.
+#define INLINE_DATA_MAX_INODE_SIZE 60
+extern char inline_data[INLINE_DATA_MAX_INODE_SIZE + XATTR_MAXSIZE];
--- a/restore/tape.c
+++ b/restore/tape.c
@@ -126,6 +126,8 @@ static int	gettingfile = 0;	/* restart h
 char		*host = NULL;
 
 static int	ofile;
+char inline_data[INLINE_DATA_MAX_INODE_SIZE + XATTR_MAXSIZE];
+
 static char	*map;
 static char	lnkbuf[MAXPATHLEN + 1];
 static int	pathlen;
@@ -1040,16 +1042,26 @@ extractfile(struct entry *ep, int doremo
 				skipfile();
 				return (FAIL);
 			}
-			getfile(xtrfile, xtrskip);
+			if (curfile.dip->di_flags & EXT4_INLINE_DATA_FL) {
+				uint64_t inline_filesize = curfile.dip->di_size>sizeof(inline_data)?sizeof(inline_data):curfile.dip->di_size;
+				memcpy(inline_data, curfile.dip->di_db, INLINE_DATA_MAX_INODE_SIZE);
+				getfile(xtrfile, xtrnull);
+				extractattr(name);
+				xtrfile(inline_data, inline_filesize);
+			} else {
+				getfile(xtrfile, xtrskip);
+				extractattr(name);
+			}
 			(void) close(ofile);
 		}
-		else
+		else {
 			skipfile();
+			extractattr(name);
+		}
 		if (chown(name, luid, lgid) < 0)
 			warn("%s: chown", name);
 		if (chmod(name, mode) < 0)
 			warn("%s: chmod", name);
-		extractattr(name);
 		utimes(name, timep);
 		if (flags)
 #ifdef	__linux__
@@ -1936,7 +1948,16 @@ comparefile(char *name)
 			 * xtrcmpfile works off mem buffer.
 			 */
 			ofile = -1;
-			getfile(xtrcmpfile, xtrcmpskip);
+			if (curfile.dip->di_flags & EXT4_INLINE_DATA_FL) {
+				uint64_t inline_filesize = curfile.dip->di_size>sizeof(inline_data)?sizeof(inline_data):curfile.dip->di_size;
+				memcpy(inline_data, curfile.dip->di_db, INLINE_DATA_MAX_INODE_SIZE);
+				getfile(xtrcmpfile, xtrnull);
+				compareattr(name);
+				xtrcmpfile(inline_data, inline_filesize);
+			} else {
+				getfile(xtrcmpfile, xtrcmpskip);
+				compareattr(name);
+			}
 			if (!cmperror) {
 				char c;
 				if (read(ifile, &c, 1) != 0) {
@@ -1963,7 +1984,16 @@ comparefile(char *name)
 			panic("cannot create file temp file %s: %s\n",
 			      name, strerror(errno));
 		}
-		getfile(xtrfile, xtrskip);
+		if (curfile.dip->di_flags & EXT4_INLINE_DATA_FL) {
+			uint64_t inline_filesize = curfile.dip->di_size>sizeof(inline_data)?sizeof(inline_data):curfile.dip->di_size;
+			memcpy(inline_data, curfile.dip->di_db, INLINE_DATA_MAX_INODE_SIZE);
+			getfile(xtrcmpfile, xtrnull);
+			compareattr(name);
+			xtrfile(inline_data, inline_filesize);
+		} else {
+			getfile(xtrfile, xtrskip);
+			compareattr(name);
+		}
 		(void) close(ofile);
 #ifdef COMPARE_FAIL_KEEP_FILE
 		if (cmpfiles(tmpfile, name, &sb))
@@ -1973,7 +2003,6 @@ comparefile(char *name)
 		unlink(tmpfile);
 #endif
 #endif /* COMPARE_ONTHEFLY */
-		compareattr(name);
 		return;
 	}
 	/* NOTREACHED */
--- a/restore/xattr.c
+++ b/restore/xattr.c
@@ -59,7 +59,8 @@
 #define EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT	3
 #define EXT2_XATTR_INDEX_TRUSTED		4
 #define	EXT2_XATTR_INDEX_LUSTRE			5
-#define EXT2_XATTR_INDEX_SECURITY	        6
+#define EXT2_XATTR_INDEX_SECURITY		6
+#define EXT2_XATTR_INDEX_SYSTEM			7
 
 struct ext2_xattr_header {
 	u_int32_t	h_magic;	/* magic number for identification */
@@ -314,7 +315,10 @@ fail:
 static int
 xattr_cb_list(char *name, char *value, int valuelen, int isSELinux, void *private)
 {
-	(void) isSELinux;
+  if (strcmp(name, "system.data") == 0) {
+		return GOOD;
+	}
+  (void) isSELinux;
 	value[valuelen] = '\0';
 	printf("EA: %s:%s\n", name, value);
 
@@ -327,6 +331,10 @@ xattr_cb_set(char *name, char *value, in
 	char *path = (char *)private;
 	int err;
 
+	if (strcmp(name, "system.data") == 0) {
+		memcpy(inline_data + INLINE_DATA_MAX_INODE_SIZE, value, valuelen);
+		return GOOD;
+	}
 	if (Nflag)
 		return GOOD;
 
@@ -353,6 +361,10 @@ xattr_cb_compare(char *name, char *value
 	char valuef[XATTR_MAXSIZE];
 	int valuesz;
 
+	if (strcmp(name, "system.data") == 0) {
+		memcpy(inline_data + INLINE_DATA_MAX_INODE_SIZE, value, valuelen);
+    return GOOD;
+	}
 	(void) isSELinux;
 #ifdef TRANSSELINUX			/*GAN6May06 SELinux MLS */
 	if (isSELinux)
@@ -439,8 +451,15 @@ xattr_count(char *buffer, int *count)
 	/* list the attribute names */
 	for (entry = FIRST_ENTRY(buffer); !IS_LAST_ENTRY(entry);
 	     entry = EXT2_XATTR_NEXT(entry))
-		result++;
-
+		switch (entry->e_name_index) {
+			case EXT2_XATTR_INDEX_SYSTEM:
+				if (entry->e_name_len == 4 && strncmp(entry->e_name, "data", 4)==0)
+					break;
+				/* FALLTHROUGH */
+			default:
+				result++;
+				break;
+		}
 	*count = result;
 	return GOOD;
 }
@@ -477,6 +496,9 @@ xattr_walk(char *buffer, int (*xattr_cb)
 		case EXT2_XATTR_INDEX_LUSTRE:
 			strcpy(name, "lustre.");
 			break;
+		case EXT2_XATTR_INDEX_SYSTEM:
+			strcpy(name, "system.");
+			break;
 		case EXT2_XATTR_INDEX_SECURITY:
 			strcpy(name, "security.");
 #ifdef TRANSSELINUX			/*GAN6May06 SELinux MLS */
