Backporting tmprotect fixes to TrueNAS Core 13.3
macOS Tahoe changed how Time Machine accesses its backup history, breaking completion detection in TrueNAS Core’s tmprotect module.
This post describes how to backport the Time Machine snapshot fixes to TrueNAS Core 13.3-U1.2, which ships with Samba 4.19.6.
Build environment
Run the host commands in a root sh shell. Check the installed version and create a build jail:
sudo sh
pkg query '%n-%v' samba
/usr/local/sbin/smbd --version
iocage create -n tmprotect-build -r 13.3-RELEASE dhcp=on vnet=on bpf=on boot=off
iocage start tmprotect-build
iocage exec tmprotect-build env IGNORE_OSVERSION=yes pkg bootstrap -f
iocage exec tmprotect-build env IGNORE_OSVERSION=yes pkg install -y \
ca_root_nss \
git \
autoconf \
automake \
bison \
flex \
gmake \
pkgconf \
perl5 \
p5-Parse-Yapp \
p5-JSON \
python311 \
py311-packaging \
patchelf \
libtool \
libarchive \
libsunacl \
libiconv \
gamin \
openldap26-client \
gnutls \
icu \
gettext-tools \
libxml2 \
lmdb \
libinotify \
expat \
jansson \
popt \
readline
The FreeBSD package repository targets 13.5, hence the override in the 13.3 jail. The commands assume the 13.3 release image is available to iocage.
Samba’s ZFS support needs libraries for linking. Copy CORE’s installed versions into the jail to match the NAS’s ZFS ABI. Adjust the pool name to match your iocage location:
jail_root=/mnt/pool1/iocage/jails/tmprotect-build/root
mkdir -p "$jail_root/usr/local/include/libzfs" "$jail_root/usr/local/include/libspl"
for lib in libzfs.so.4 libzfs_core.so.3 libnvpair.so.3 libuutil.so.3; do
cp -L "/usr/local/lib/$lib" "$jail_root/usr/local/lib/"
ln -sf "$lib" "$jail_root/usr/local/lib/${lib%.*}"
done
iocage console -f tmprotect-build
Sources and headers
Inside the jail, fetch the Samba revision pinned by the TrueNAS port recipe, along with the two upstream fixes:
mkdir -p /root/build
cd /root/build
git clone --revision=4fec43c0c0155240da48c33ce7dc1abcf7d8c716 --depth=1 https://github.com/truenas/samba.git
cd samba
git fetch --depth=1 --filter=blob:none origin 1e461935f89b1a058c9005c74e99c895a0673b81 3fba25ef9feb427cde1534075bb387236e38f747
cd ..
CORE’s ZFS include directories are empty. Fetch the matching ZFS revision and generate the headers:
git clone --revision=4f2aa13822f0db5e9b94a76f3a6b5165a282b30b --depth=1 https://github.com/truenas/zfs.git
cd zfs
./autogen.sh
MAKE=gmake ./configure --prefix=/usr/local --with-config=user --without-python --disable-dependency-tracking
cp -R include/. zfs_config.h /usr/local/include/libzfs/
cp -R lib/libspl/include/. lib/libspl/include/os/freebsd/. /usr/local/include/libspl/
cd ..
Patches
Apply the Tahoe history-access fix and deferred snapshot trigger:
cd samba
git show --format= 1e461935f89b1a058c9005c74e99c895a0673b81 -- source3/modules/vfs_tmprotect.c | git apply
# Remove CORE's redundant guard so the deferred-trigger patch applies.
perl -0pi \
-e 's/if \(\(config->history_file == NULL\) \|\| \(last_snap \+ 900 > curtime\)\)/if (last_snap + 900 > curtime)/' \
source3/modules/vfs_tmprotect.c
git show --format= 3fba25ef9feb427cde1534075bb387236e38f747 -- source3/modules/vfs_tmprotect.c | git apply
CORE needs FreeBSD descriptor-path resolution, and Samba 4.19’s rename callback takes fewer arguments. Apply these adjustments:
patch -p1 <<'PATCH'
--- a/source3/modules/vfs_tmprotect.c
+++ b/source3/modules/vfs_tmprotect.c
@@ -27,0 +28,3 @@
+#ifdef FREEBSD
+#include <sys/user.h>
+#endif
@@ -305,2 +308 @@
-static bool get_history_full_path(const char *connectpath,
- const struct files_struct *fsp,
+static bool get_history_full_path(int fd,
@@ -309,3 +311,13 @@
- config->history_file = talloc_asprintf(config, "%s/%s",
- connectpath,
- fsp->fsp_name->base_name);
+ struct kinfo_file info;
+ ZERO_STRUCT(info);
+ info.kf_structsize = sizeof(info);
+ if (fcntl(fd, F_KINFO, &info) == -1) {
+ DBG_ERR("Failed to resolve history path from fd %d: %s\n",
+ fd, strerror(errno));
+ return false;
+ }
+ if (info.kf_path[0] != '/') {
+ errno = ENOENT;
+ return false;
+ }
+ config->history_file = talloc_strdup(config, info.kf_path);
@@ -361 +373 @@
- ok = get_history_full_path(handle->conn->connectpath, fsp, config);
+ ok = get_history_full_path(ret, config);
@@ -440,0 +453,4 @@
+ if (snapshot_name == NULL) {
+ DBG_ERR("talloc_asprintf() failed\n");
+ return;
+ }
@@ -491,2 +507 @@
- const struct smb_filename *smb_fname_dst,
- const struct vfs_rename_how *how)
+ const struct smb_filename *smb_fname_dst)
@@ -502 +517 @@
- dstfsp, smb_fname_dst, how);
+ dstfsp, smb_fname_dst);
PATCH
Build
Configure with CORE’s build options, compile the module, and return to the host:
export CFLAGS='-D_ACL_PRIVATE -D_FREEBSD_LIBZFS -I/usr/local/include -fno-omit-frame-pointer -fno-color-diagnostics'
export CPPFLAGS='-I/usr/local/include'
export LDFLAGS='-L/usr/local/lib -Wl,--undefined-version'
export PYTHONHASHSEED=1
python3.11 buildtools/bin/waf configure \
--prefix=/usr/local \
--sysconfdir=/usr/local/etc \
--includedir=/usr/local/include/samba4 \
--libdir=/usr/local/lib/samba4 \
--with-privatelibdir=/usr/local/lib/samba4/private \
--with-modulesdir=/usr/local/lib/shared-modules \
--localstatedir=/var \
--with-piddir=/var/run/samba4 \
--with-lockdir=/var/run/samba4 \
--with-statedir=/var/db/system/samba4 \
--with-cachedir=/var/run/samba4 \
--with-privatedir=/var/db/system/samba4/private \
--with-logfilebase=/var/log/samba4 \
--with-pam \
--with-iconv \
--with-winbind \
--with-regedit \
--disable-rpath \
--without-lttng \
--without-gettext \
--enable-pthreadpool \
--without-fake-kaserver \
--without-systemd \
--with-libarchive \
--with-acl-support \
--with-sendfile-support \
--without-ldb-lmdb \
--without-ad-dc \
--with-ldap \
--with-profiling-data \
--with-quotas \
--with-syslog \
--with-utmp \
--enable-spotlight \
--with-fam \
--with-libzfs \
--bundled-libraries='cmocka,talloc,tevent,tdb,ldb,replace,com_err,!libarchive' \
--with-shared-modules='idmap_nss,idmap_autorid,idmap_rid,idmap_hash,idmap_tdb,idmap_tdb2,idmap_script,nss_info_hash,idmap_ad,idmap_rfc2307,nss_info_template,nss_info_rfc2307,nss_info_sfu,nss_info_sfu20,vfs_cacheprime,vfs_zfs_space,vfs_ixnas,vfs_shadow_copy_zfs,vfs_noacl,vfs_tmprotect,vfs_zfs_fsrvp,vfs_aio_fbsd'
python3.11 buildtools/bin/waf build --targets=vfs_tmprotect -j2
cp bin/default/source3/modules/libvfs_module_tmprotect.so /root/build/tmprotect.so
strip --strip-unneeded /root/build/tmprotect.so
patchelf --set-soname tmprotect.so --set-rpath /usr/local/lib/samba4/private /root/build/tmprotect.so
exit
Install
Back on the host, back up the original module, install the replacement, and restart SMB between backups:
cd /usr/local/lib/shared-modules/vfs
cp -p tmprotect.so "tmprotect.so.$(date +%Y%m%dT%H%M%S)"
ldd "$jail_root/root/build/tmprotect.so"
install -S -o root -g wheel -m 0755 "$jail_root/root/build/tmprotect.so" tmprotect.so
midclt call service.restart cifs