Package: zfs-linux
Version: 0.7.5-1
Severity: normal

--- Please enter the report below this line. ---
A longstanding incorrect calculation of free memory, since
Linux 3.14, has been fixed in upstream master.

I have submitted a PR to upstream that re-bases the patch
onto the 0.7.6 stable release [1].

Additionally, the fix introduces a new file needed during
module build, scripts/enum-extract.pl, which must therefore
be packaged with the dkms source. All of this has included
in the attached patch, and on [2] in pulls/proposed-0.7.6.

That branch also includes a decompression out of bounds
check, as well as the 0.7.6 release import.

[1] https://github.com/zfsonlinux/zfs/pull/7238
[2] https://github.com/aerusso/zfs
commit 87d8977aa53192a647c8bdc99e14a6ab6dfd5560
Author: Antonio Russo <antonio.e.ru...@gmail.com>
Date:   Sun Feb 25 21:18:10 2018 -0500

    Linux 3.14 compat: fix free memory calculation

diff --git a/debian/patches/00004-compat-fix_free_memory_calculation.patch b/debian/patches/00004-compat-fix_free_memory_calculation.patch
new file mode 100644
index 00000000..4cadf854
--- /dev/null
+++ b/debian/patches/00004-compat-fix_free_memory_calculation.patch
@@ -0,0 +1,425 @@
+commit 697dd9746d93740d81c4f5a722b03aa0d9344b0b
+Author: chrisrd <ch...@onthe.net.au>
+Date:   Sat Feb 24 03:50:06 2018 +1100
+
+    Fix free memory calculation on v3.14+
+    
+    Provide infrastructure to auto-configure to enum and API changes in the
+    global page stats used for our free memory calculations.
+    
+    arc_free_memory has been broken since an API change in Linux v3.14:
+    
+    2016-07-28 v4.8 599d0c95 mm, vmscan: move LRU lists to node
+    2016-07-28 v4.8 75ef7184 mm, vmstat: add infrastructure for per-node
+      vmstats
+    
+    These commits moved some of global_page_state() into
+    global_node_page_state(). The API change was particularly egregious as,
+    instead of breaking the old code, it silently did the wrong thing and we
+    continued using global_page_state() where we should have been using
+    global_node_page_state(), thus indexing into the wrong array via
+    NR_SLAB_RECLAIMABLE et al.
+    
+    There have been further API changes along the way:
+    
+    2017-07-06 v4.13 385386cf mm: vmstat: move slab statistics from zone to
+      node counters
+    2017-09-06 v4.14 c41f012a mm: rename global_page_state to
+      global_zone_page_state
+    
+    ...and various (incomplete, as it turns out) attempts to accomodate
+    these changes in ZoL:
+    
+    2017-08-24 2209e409 Linux 4.8+ compatibility fix for vm stats
+    2017-09-16 787acae0 Linux 3.14 compat: IO acct, global_page_state, etc
+    2017-09-19 661907e6 Linux 4.14 compat: IO acct, global_page_state, etc
+    
+    The config infrastructure provided here resolves these issues going back
+    to the original API change in v3.14 and is robust against further Linux
+    changes in this area.
+    
+    Reviewed-by: Giuseppe Di Natale <dinata...@llnl.gov>
+    Reviewed-by: Brian Behlendorf <behlendo...@llnl.gov>
+    Reviewed-by: George Melikov <m...@gmelikov.ru>
+    Signed-off-by: Chris Dunlop <ch...@onthe.net.au>
+    Closes #7170
+
+diff --git a/config/kernel-global_page_state.m4 b/config/kernel-global_page_state.m4
+new file mode 100644
+index 000000000..f4a40011f
+--- /dev/null
++++ b/config/kernel-global_page_state.m4
+@@ -0,0 +1,109 @@
++dnl #
++dnl # 4.8 API change
++dnl #
++dnl # 75ef71840539 mm, vmstat: add infrastructure for per-node vmstats
++dnl # 599d0c954f91 mm, vmscan: move LRU lists to node
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_NODE_PAGE_STATE], [
++	AC_MSG_CHECKING([whether global_node_page_state() exists])
++	ZFS_LINUX_TRY_COMPILE([
++		#include <linux/mm.h>
++		#include <linux/vmstat.h>
++	],[
++		(void) global_node_page_state(0);
++	],[
++		AC_MSG_RESULT(yes)
++		AC_DEFINE(ZFS_GLOBAL_NODE_PAGE_STATE, 1, [global_node_page_state() exists])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
++dnl #
++dnl # 4.14 API change
++dnl #
++dnl # c41f012ade0b mm: rename global_page_state to global_zone_page_state
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE], [
++	AC_MSG_CHECKING([whether global_zone_page_state() exists])
++	ZFS_LINUX_TRY_COMPILE([
++		#include <linux/mm.h>
++		#include <linux/vmstat.h>
++	],[
++		(void) global_zone_page_state(0);
++	],[
++		AC_MSG_RESULT(yes)
++		AC_DEFINE(ZFS_GLOBAL_ZONE_PAGE_STATE, 1, [global_zone_page_state() exists])
++	],[
++		AC_MSG_RESULT(no)
++	])
++])
++
++dnl #
++dnl # Create a define and autoconf variable for an enum member
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_ENUM_MEMBER], [
++	AC_MSG_CHECKING([whether enum $2 contains $1])
++	AS_IF([AC_TRY_COMMAND("${srcdir}/scripts/enum-extract.pl" "$2" "$3" | egrep -qx $1)],[
++		AC_MSG_RESULT([yes])
++		AC_DEFINE(m4_join([_], [ZFS_ENUM], m4_toupper($2), $1), 1, [enum $2 contains $1])
++		m4_join([_], [ZFS_ENUM], m4_toupper($2), $1)=1
++	],[
++		AC_MSG_RESULT([no])
++	])
++])
++
++dnl #
++dnl # Sanity check helpers
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR],[
++	AC_MSG_RESULT(no)
++	AC_MSG_RESULT([$1 in either node_stat_item or zone_stat_item: $2])
++	AC_MSG_RESULT([configure needs updating, see: config/kernel-global_page_state.m4])
++	AC_MSG_FAILURE([SHUT 'ER DOWN CLANCY, SHE'S PUMPIN' MUD!])
++])
++
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK], [
++	enum_check_a="m4_join([_], [$ZFS_ENUM_NODE_STAT_ITEM], $1)"
++	enum_check_b="m4_join([_], [$ZFS_ENUM_ZONE_STAT_ITEM], $1)"
++	AS_IF([test -n "$enum_check_a" -a -n "$enum_check_b"],[
++		ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR([$1], [DUPLICATE])
++	])
++	AS_IF([test -z "$enum_check_a" -a -z "$enum_check_b"],[
++		ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR([$1], [NOT FOUND])
++	])
++])
++
++dnl #
++dnl # Ensure the config tests are finding one and only one of each enum of interest
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE_SANITY], [
++	AC_MSG_CHECKING([global_page_state enums are sane])
++
++	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_FILE_PAGES])
++	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_INACTIVE_ANON])
++	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_INACTIVE_FILE])
++	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_SLAB_RECLAIMABLE])
++
++	AC_MSG_RESULT(yes)
++])
++
++dnl #
++dnl # enum members in which we're interested
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE], [
++	ZFS_AC_KERNEL_GLOBAL_NODE_PAGE_STATE
++	ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE
++
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_FILE_PAGES],		[node_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_ANON],		[node_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_FILE],		[node_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_SLAB_RECLAIMABLE],	[node_stat_item], [$LINUX/include/linux/mmzone.h])
++
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_FILE_PAGES],		[zone_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_ANON],		[zone_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_FILE],		[zone_stat_item], [$LINUX/include/linux/mmzone.h])
++	ZFS_AC_KERNEL_ENUM_MEMBER([NR_SLAB_RECLAIMABLE],	[zone_stat_item], [$LINUX/include/linux/mmzone.h])
++
++	ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE_SANITY
++])
+diff --git a/config/kernel-vm_node_stat.m4 b/config/kernel-vm_node_stat.m4
+deleted file mode 100644
+index e1c42f884..000000000
+--- a/config/kernel-vm_node_stat.m4
++++ /dev/null
+@@ -1,22 +0,0 @@
+-dnl #
+-dnl # 4.8 API change
+-dnl # kernel vm counters change
+-dnl #
+-AC_DEFUN([ZFS_AC_KERNEL_VM_NODE_STAT], [
+-	AC_MSG_CHECKING([whether to use vm_node_stat based fn's])
+-	ZFS_LINUX_TRY_COMPILE([
+-		#include <linux/mm.h>
+-		#include <linux/vmstat.h>
+-        ],[
+-			int a __attribute__ ((unused)) = NR_VM_NODE_STAT_ITEMS;
+-			long x __attribute__ ((unused)) =
+-				atomic_long_read(&vm_node_stat[0]);
+-			(void) global_node_page_state(0);
+-	],[
+-		AC_MSG_RESULT(yes)
+-		AC_DEFINE(ZFS_GLOBAL_NODE_PAGE_STATE, 1,
+-			[using global_node_page_state()])
+-	],[
+-		AC_MSG_RESULT(no)
+-	])
+-])
+diff --git a/config/kernel.m4 b/config/kernel.m4
+index b759ccd39..c1d2dbaab 100644
+--- a/config/kernel.m4
++++ b/config/kernel.m4
+@@ -122,7 +122,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [
+ 	ZFS_AC_KERNEL_RENAME_WANTS_FLAGS
+ 	ZFS_AC_KERNEL_HAVE_GENERIC_SETXATTR
+ 	ZFS_AC_KERNEL_CURRENT_TIME
+-	ZFS_AC_KERNEL_VM_NODE_STAT
++	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE
+ 
+ 	AS_IF([test "$LINUX_OBJ" != "$LINUX"], [
+ 		KERNELMAKE_PARAMS="$KERNELMAKE_PARAMS O=$LINUX_OBJ"
+diff --git a/include/linux/Makefile.am b/include/linux/Makefile.am
+index 9bb0b3493..89c2689f6 100644
+--- a/include/linux/Makefile.am
++++ b/include/linux/Makefile.am
+@@ -9,7 +9,8 @@ KERNEL_H = \
+ 	$(top_srcdir)/include/linux/kmap_compat.h \
+ 	$(top_srcdir)/include/linux/simd_x86.h \
+ 	$(top_srcdir)/include/linux/simd_aarch64.h \
+-	$(top_srcdir)/include/linux/mod_compat.h
++	$(top_srcdir)/include/linux/mod_compat.h \
++	$(top_srcdir)/include/linux/page_compat.h
+ 
+ USER_H =
+ 
+diff --git a/include/linux/page_compat.h b/include/linux/page_compat.h
+new file mode 100644
+index 000000000..95acb7d53
+--- /dev/null
++++ b/include/linux/page_compat.h
+@@ -0,0 +1,78 @@
++#ifndef	_ZFS_PAGE_COMPAT_H
++#define	_ZFS_PAGE_COMPAT_H
++
++/*
++ * We have various enum members moving between two separate enum types,
++ * and accessed by different functions at various times. Centralise the
++ * insanity.
++ *
++ * < v4.8: all enums in zone_stat_item, via global_page_state()
++ * v4.8: some enums moved to node_stat_item, global_node_page_state() introduced
++ * v4.13: some enums moved from zone_stat_item to node_state_item
++ * v4.14: global_page_state() rename to global_zone_page_state()
++ *
++ * The defines used here are created by config/kernel-global_page_state.m4
++ */
++
++/*
++ * Create our own accessor functions to follow the Linux API changes
++ */
++#if	defined(ZFS_GLOBAL_ZONE_PAGE_STATE)
++
++/* global_zone_page_state() introduced */
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_FILE_PAGES)
++#define	nr_file_pages() global_node_page_state(NR_FILE_PAGES)
++#else
++#define	nr_file_pages() global_zone_page_state(NR_FILE_PAGES)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_ANON)
++#define	nr_inactive_anon_pages() global_node_page_state(NR_INACTIVE_ANON)
++#else
++#define	nr_inactive_anon_pages() global_zone_page_state(NR_INACTIVE_ANON)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_FILE)
++#define	nr_inactive_file_pages() global_node_page_state(NR_INACTIVE_FILE)
++#else
++#define	nr_inactive_file_pages() global_zone_page_state(NR_INACTIVE_FILE)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_SLAB_RECLAIMABLE)
++#define	nr_slab_reclaimable_pages() global_node_page_state(NR_SLAB_RECLAIMABLE)
++#else
++#define	nr_slab_reclaimable_pages() global_zone_page_state(NR_SLAB_RECLAIMABLE)
++#endif
++
++#elif	defined(ZFS_GLOBAL_NODE_PAGE_STATE)
++
++/* global_node_page_state() introduced */
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_FILE_PAGES)
++#define	nr_file_pages() global_node_page_state(NR_FILE_PAGES)
++#else
++#define	nr_file_pages() global_page_state(NR_FILE_PAGES)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_ANON)
++#define	nr_inactive_anon_pages() global_node_page_state(NR_INACTIVE_ANON)
++#else
++#define	nr_inactive_anon_pages() global_page_state(NR_INACTIVE_ANON)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_FILE)
++#define	nr_inactive_file_pages() global_node_page_state(NR_INACTIVE_FILE)
++#else
++#define	nr_inactive_file_pages() global_page_state(NR_INACTIVE_FILE)
++#endif
++#if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_SLAB_RECLAIMABLE)
++#define	nr_slab_reclaimable_pages() global_node_page_state(NR_SLAB_RECLAIMABLE)
++#else
++#define	nr_slab_reclaimable_pages() global_page_state(NR_SLAB_RECLAIMABLE)
++#endif
++
++#else
++
++/* global_page_state() only */
++#define	nr_file_pages()			global_page_state(NR_FILE_PAGES)
++#define	nr_inactive_anon_pages()	global_page_state(NR_INACTIVE_ANON)
++#define	nr_inactive_file_pages()	global_page_state(NR_INACTIVE_FILE)
++#define	nr_slab_reclaimable_pages()	global_page_state(NR_SLAB_RECLAIMABLE)
++
++#endif /* ZFS_GLOBAL_ZONE_PAGE_STATE */
++
++#endif /* _ZFS_PAGE_COMPAT_H */
+diff --git a/module/zfs/arc.c b/module/zfs/arc.c
+index 264e67735..bb39f9c80 100644
+--- a/module/zfs/arc.c
++++ b/module/zfs/arc.c
+@@ -280,6 +280,7 @@
+ #include <sys/fs/swapnode.h>
+ #include <sys/zpl.h>
+ #include <linux/mm_compat.h>
++#include <linux/page_compat.h>
+ #endif
+ #include <sys/callb.h>
+ #include <sys/kstat.h>
+@@ -4017,17 +4018,11 @@ arc_free_memory(void)
+ 	si_meminfo(&si);
+ 	return (ptob(si.freeram - si.freehigh));
+ #else
+-#ifdef ZFS_GLOBAL_NODE_PAGE_STATE
+ 	return (ptob(nr_free_pages() +
+-	    global_node_page_state(NR_INACTIVE_FILE) +
+-	    global_node_page_state(NR_INACTIVE_ANON) +
+-	    global_node_page_state(NR_SLAB_RECLAIMABLE)));
+-#else
+-	return (ptob(nr_free_pages() +
+-	    global_page_state(NR_INACTIVE_FILE) +
+-	    global_page_state(NR_INACTIVE_ANON) +
+-	    global_page_state(NR_SLAB_RECLAIMABLE)));
+-#endif /* ZFS_GLOBAL_NODE_PAGE_STATE */
++	    nr_inactive_file_pages() +
++	    nr_inactive_anon_pages() +
++	    nr_slab_reclaimable_pages()));
++
+ #endif /* CONFIG_HIGHMEM */
+ #else
+ 	return (spa_get_random(arc_all_memory() * 20 / 100));
+@@ -4438,13 +4433,7 @@ arc_evictable_memory(void)
+ 	 * Scale reported evictable memory in proportion to page cache, cap
+ 	 * at specified min/max.
+ 	 */
+-#ifdef ZFS_GLOBAL_NODE_PAGE_STATE
+-	uint64_t min = (ptob(global_node_page_state(NR_FILE_PAGES)) / 100) *
+-	    zfs_arc_pc_percent;
+-#else
+-	uint64_t min = (ptob(global_page_state(NR_FILE_PAGES)) / 100) *
+-	    zfs_arc_pc_percent;
+-#endif
++	uint64_t min = (ptob(nr_file_pages()) / 100) * zfs_arc_pc_percent;
+ 	min = MAX(arc_c_min, MIN(arc_c_max, min));
+ 
+ 	if (arc_dirty >= min)
+diff --git a/scripts/Makefile.am b/scripts/Makefile.am
+index 74b8b31a5..e083fae0f 100644
+--- a/scripts/Makefile.am
++++ b/scripts/Makefile.am
+@@ -1,6 +1,6 @@
+ SUBDIRS = zpool-config zpios-test zpios-profile
+ 
+-EXTRA_DIST = dkms.mkconf dkms.postbuild kmodtool zfs2zol-patch.sed cstyle.pl
++EXTRA_DIST = dkms.mkconf dkms.postbuild enum-extract.pl kmodtool zfs2zol-patch.sed cstyle.pl
+ 
+ pkgdatadir = $(datadir)/@PACKAGE@
+ dist_pkgdata_SCRIPTS = \
+diff --git a/scripts/enum-extract.pl b/scripts/enum-extract.pl
+new file mode 100755
+index 000000000..5112cc807
+--- /dev/null
++++ b/scripts/enum-extract.pl
+@@ -0,0 +1,58 @@
++#!/usr/bin/perl -w
++
++my $usage = <<EOT;
++usage: config-enum enum [file ...]
++
++Returns the elements from an enum declaration.
++
++"Best effort": we're not building an entire C interpreter here!
++EOT
++
++use warnings;
++use strict;
++use Getopt::Std;
++
++my %opts;
++
++if (!getopts("", \%opts) || @ARGV < 1) {
++	print $usage;
++	exit 2;
++}
++
++my $enum = shift;
++
++my $in_enum = 0;
++
++while (<>) {
++	# comments
++	s/\/\*.*\*\///;
++	if (m/\/\*/) {
++		while ($_ .= <>) {
++			last if s/\/\*.*\*\///s;
++		}
++	}
++
++	# preprocessor stuff
++	next if /^#/;
++
++	# find our enum
++	$in_enum = 1 if s/^\s*enum\s+${enum}(?:\s|$)//;
++	next unless $in_enum;
++
++	# remove explicit values
++	s/\s*=[^,]+,/,/g;
++
++	# extract each identifier
++	while (m/\b([a-z_][a-z0-9_]*)\b/ig) {
++		print $1, "\n";
++	}
++
++	#
++	# don't exit: there may be multiple versions of the same enum, e.g.
++	# inside different #ifdef blocks. Let's explicitly return all of
++	# them and let external tooling deal with it.
++	#
++	$in_enum = 0 if m/}\s*;/;
++}
++
++exit 0;
diff --git a/debian/patches/series b/debian/patches/series
index 2cb08360..8f8b19a2 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,6 +1,7 @@
 00001-compat-4.16-inode_set_iversion.patch
 00002-compat-4.11-refcount.patch
 00003-fix_lze_out_of_bounds.patch
+00004-compat-fix_free_memory_calculation.patch
 0001-Prevent-manual-builds-in-the-DKMS-source.patch
 0002-Check-for-META-and-DCH-consistency-in-autoconf.patch
 0003-Add-libuutil-to-LIBADD-for-libzfs-and-libzfs_core.patch
diff --git a/debian/rules b/debian/rules
index f445b58c..1e9082ac 100755
--- a/debian/rules
+++ b/debian/rules
@@ -80,7 +80,8 @@ override_dh_auto_install:
 
 	@# Install the DKMS source.
 	@# We only want the files needed to build the modules
-	mkdir -p '$(CURDIR)/debian/tmp/usr/src/$(NAME)-$(DEB_VERSION_UPSTREAM)'
+	mkdir -p '$(CURDIR)/debian/tmp/usr/src/$(NAME)-$(DEB_VERSION_UPSTREAM)/scripts'
+	cp '$(CURDIR)/scripts/enum-extract.pl' '$(CURDIR)/debian/tmp/usr/src/$(NAME)-$(DEB_VERSION_UPSTREAM)/scripts'
 	$(foreach file,$(DKMSFILES),mv '$(CURDIR)/$(NAME)-$(DEB_VERSION_UPSTREAM)/$(file)' '$(CURDIR)/debian/tmp/usr/src/$(NAME)-$(DEB_VERSION_UPSTREAM)' || exit 1;)
 	@# Hellish awk line:
 	@#  * Deletes from configure.ac the parts not needed for building the kernel module

Reply via email to