Blob Blame History Raw
diff --git a/Makefile.in b/Makefile.in
index b43d2d6..6eff522 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -42,7 +42,7 @@ LOCALSTATE=var/pseudo
 BINDIR=$(PREFIX)/$(BIN)
 LOCALSTATEDIR=$(PREFIX)/$(LOCALSTATE)
 
-CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra
+CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra -Wno-deprecated-declarations
 CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE $(ARCH_FLAGS)
 CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS) $(EPOLL)
 CFLAGS_DEBUG=-O2 -g
@@ -75,6 +75,7 @@ TABLES=table_templates/pseudo_tables.c table_templates/pseudo_tables.h
 all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE)
 
 test: all | $(BIN) $(LIB) $(LOCALSTATE)
+	$(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o test/test-rename-fstat test/test-rename-fstat.c
 	@./run_tests.sh -v
 
 install-lib: $(LIBPSEUDO)
diff --git a/enums/res.in b/enums/res.in
index 435338f..b0096b4 100644
--- a/enums/res.in
+++ b/enums/res.in
@@ -2,3 +2,4 @@ res: RESULT
 succeed
 fail
 error
+abort
diff --git a/maketables b/maketables
index a211772..52285e2 100755
--- a/maketables
+++ b/maketables
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (c) 2008-2010, 2013 Wind River Systems, Inc.
 #
diff --git a/makewrappers b/makewrappers
index e84607d..6681e11 100755
--- a/makewrappers
+++ b/makewrappers
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (c) 2008-2011,2013 Wind River Systems, Inc.
 #
@@ -11,6 +11,7 @@ import glob
 import sys
 import re
 import os.path
+import platform
 import string
 import subprocess
 from templatefile import TemplateFile
@@ -228,6 +229,8 @@ class Function:
         self.real_func = None
         self.paths_to_munge = []
         self.specific_dirfds = {}
+        self.fd_arg = False
+        self.noignore_path = False
         self.hand_wrapped = None
         self.async_skip = None
         # used for the copyright date when creating stub functions
@@ -267,6 +270,11 @@ class Function:
                 self.flags = '(flags & AT_SYMLINK_NOFOLLOW)'
             elif arg.name.endswith('path'):
                 self.paths_to_munge.append(arg.name)
+            elif arg.name == 'fd':
+                self.fd_arg = "fd"
+            elif arg.name == 'filedes':
+                self.fd_arg = "filedes"
+
     
         # pick default values
         if self.type == 'void':
@@ -283,10 +291,18 @@ class Function:
 
         # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW
         if self.comments:
-            modifiers = self.comments.split(', ')
-            for mod in modifiers:
-                key, value = mod.split('=')
-                value = value.rstrip()
+            # Build a dictionary of key=value, key=value pairs
+            modifiers = dict(mod.split("=") for mod in self.comments.split(','))
+            # Strip all leading/trailing whitespace
+            modifiers = {k.strip():v.strip() for k, v in modifiers.items()}
+
+            arch = "-" + platform.machine()
+            # Sorted so that versions-foo appear after versions, so overrides are easy
+            for key in sorted(modifiers):
+                value = modifiers[key]
+                # If the key is version-arm64 and we're on arm64 then rename this to version
+                if key.endswith(arch):
+                    key = key.replace(arch, "")
                 setattr(self, key, value)
     
     def maybe_inode64(self):
@@ -361,6 +377,25 @@ class Function:
                 (path, prefix, self.dirfd, path, self.flags))
         return "\n\t\t".join(fix_paths)
 
+    def ignore_paths(self):
+        if self.noignore_path:
+            return "0"
+
+        mainpath = None
+        if "oldpath" in self.paths_to_munge:
+            mainpath = "oldpath"
+        elif "newpath" in self.paths_to_munge:
+            mainpath = "newpath"
+        elif "path" in self.paths_to_munge:
+            mainpath = "path"
+
+        if mainpath:
+            return "pseudo_client_ignore_path(%s)" % mainpath
+        if self.fd_arg:
+            return "pseudo_client_ignore_fd(%s)" % self.fd_arg
+
+        return "0"
+
     def real_predecl(self):
         if self.real_func:
             return self.decl().replace(self.name, self.real_func, 1) + ";"
@@ -567,7 +602,7 @@ def process_wrapfuncs(port):
             func.directory = directory
             funcs[func.name] = func
             sys.stdout.write(".")
-        except Exception(e):
+        except Exception as e:
             print("Parsing failed:", e)
             exit(1)
     funclist.close()
diff --git a/ports/linux/guts/fcntl.c b/ports/linux/guts/fcntl.c
index 4dd9796..434c7f3 100644
--- a/ports/linux/guts/fcntl.c
+++ b/ports/linux/guts/fcntl.c
@@ -52,6 +52,11 @@
 	case F_GETLK:
 	case F_SETLK:
 	case F_SETLKW:
+#ifdef F_OFD_GETLK
+	case F_OFD_GETLK:
+	case F_OFD_SETLK:
+	case F_OFD_SETLKW:
+#endif
 		rc = real_fcntl(fd, cmd, lock);
 		break;
 #if defined(F_GETLK64) && (F_GETLK64 != F_GETLK)
diff --git a/ports/linux/guts/lchmod.c b/ports/linux/guts/lchmod.c
new file mode 100644
index 0000000..da330a7
--- /dev/null
+++ b/ports/linux/guts/lchmod.c
@@ -0,0 +1,14 @@
+/* 
+ * Copyright (c) 2021 Linux Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * static int
+ * wrap_lchmod(const char *path, mode_t mode) {
+ */
+
+	rc = wrap_fchmodat(AT_FDCWD, path, mode, AT_SYMLINK_NOFOLLOW);
+
+/*	return rc;
+ * }
+ */
diff --git a/ports/linux/guts/mkostemp64.c b/ports/linux/guts/mkostemp64.c
new file mode 100644
index 0000000..502211b
--- /dev/null
+++ b/ports/linux/guts/mkostemp64.c
@@ -0,0 +1,53 @@
+/* 
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * static int
+ * wrap_mkstemp64(char *template, int oflags) {
+ *	int rc = -1;
+ */
+	struct stat64 buf;
+ 	int save_errno;
+	size_t len;
+	char *tmp_template;
+
+	if (!template) {
+		errno = EFAULT;
+		return 0;
+	}
+
+	len = strlen(template);
+	tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
+
+	if (!tmp_template) {
+		errno = ENOENT;
+		return -1;
+	}
+
+	/* mkstemp64 wrapper uses this code and mkostemp64 not present in some glibc versions */
+	if (oflags == 0)
+		rc = real_mkstemp64(tmp_template);
+	else
+		rc = real_mkostemp64(tmp_template, oflags);
+
+	if (rc != -1) {
+		save_errno = errno;
+
+		if (real___fxstat64(_STAT_VER, rc, &buf) != -1) {
+			real_fchmod(rc, PSEUDO_FS_MODE(0600, 0));
+			pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf);
+			pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf);
+		} else {
+			pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n",
+				rc, strerror(errno));
+			pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0);
+		}
+		errno = save_errno;
+	}
+	/* mkstemp only changes the XXXXXX at the end. */
+	memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
+/*	return rc;
+ * }
+ */
diff --git a/ports/linux/guts/mkstemp64.c b/ports/linux/guts/mkstemp64.c
index aa7bb58..487f256 100644
--- a/ports/linux/guts/mkstemp64.c
+++ b/ports/linux/guts/mkstemp64.c
@@ -8,42 +8,9 @@
  * wrap_mkstemp64(char *template) {
  *	int rc = -1;
  */
-	struct stat64 buf;
- 	int save_errno;
-	size_t len;
-	char *tmp_template;
+	/* mkstemp64() is just like mkostemp64() with no flags */
+	rc = wrap_mkostemp64(template, 0);
 
-	if (!template) {
-		errno = EFAULT;
-		return 0;
-	}
-
-	len = strlen(template);
-	tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
-
-	if (!tmp_template) {
-		errno = ENOENT;
-		return -1;
-	}
-
-	rc = real_mkstemp64(tmp_template);
-
-	if (rc != -1) {
-		save_errno = errno;
-
-		if (real___fxstat64(_STAT_VER, rc, &buf) != -1) {
-			real_fchmod(rc, PSEUDO_FS_MODE(0600, 0));
-			pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf);
-			pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf);
-		} else {
-			pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n",
-				rc, strerror(errno));
-			pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0);
-		}
-		errno = save_errno;
-	}
-	/* mkstemp only changes the XXXXXX at the end. */
-	memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
 /*	return rc;
  * }
  */
diff --git a/ports/linux/portdefs.h b/ports/linux/portdefs.h
index d419365..9545550 100644
--- a/ports/linux/portdefs.h
+++ b/ports/linux/portdefs.h
@@ -32,3 +32,24 @@ GLIBC_COMPAT_SYMBOL(memcpy,2.0);
 
 #include <linux/capability.h>
 #include <sys/syscall.h>
+#include <sys/prctl.h>
+#include <linux/seccomp.h>
+
+#ifndef _STAT_VER
+#if defined (__aarch64__)
+#define _STAT_VER 0
+#elif defined (__x86_64__)
+#define _STAT_VER 1
+#else
+#define _STAT_VER 3
+#endif
+#endif
+#ifndef _MKNOD_VER
+#if defined (__aarch64__)
+#define _MKNOD_VER 0
+#elif defined (__x86_64__)
+#define _MKNOD_VER 0
+#else
+#define _MKNOD_VER 1
+#endif
+#endif
diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
index cd7e173..ed34115 100644
--- a/ports/linux/pseudo_wrappers.c
+++ b/ports/linux/pseudo_wrappers.c
@@ -57,6 +57,7 @@ int pseudo_capset(cap_user_header_t hdrp, const cap_user_data_t datap) {
 long
 syscall(long number, ...) {
 	long rc = -1;
+	va_list ap;
 
 	if (!pseudo_check_wrappers() || !real_syscall) {
 		/* rc was initialized to the "failure" value */
@@ -77,6 +78,20 @@ syscall(long number, ...) {
 	(void) number;
 #endif
 
+#ifdef SYS_seccomp
+	/* pseudo and seccomp are incompatible as pseudo uses different syscalls
+	 * so pretend to enable seccomp but really do nothing */
+	if (number == SYS_seccomp) {
+		unsigned long cmd;
+		va_start(ap, number);
+		cmd = va_arg(ap, unsigned long);
+		va_end(ap);
+		if (cmd == SECCOMP_SET_MODE_FILTER) {
+		    return 0;
+		}
+	}
+#endif
+
 	/* gcc magic to attempt to just pass these args to syscall. we have to
 	 * guess about the number of args; the docs discuss calling conventions
 	 * up to 7, so let's try that?
@@ -92,3 +107,44 @@ static long wrap_syscall(long nr, va_list ap) {
 	(void) ap;
 	return -1;
 }
+
+int
+prctl(int option, ...) {
+	int rc = -1;
+	va_list ap;
+
+	if (!pseudo_check_wrappers() || !real_prctl) {
+		/* rc was initialized to the "failure" value */
+		pseudo_enosys("prctl");
+		return rc;
+	}
+
+#ifdef SECCOMP_SET_MODE_FILTER
+	/* pseudo and seccomp are incompatible as pseudo uses different syscalls
+	 * so pretend to enable seccomp but really do nothing */
+	if (option == PR_SET_SECCOMP) {
+		unsigned long cmd;
+		va_start(ap, option);
+		cmd = va_arg(ap, unsigned long);
+		va_end(ap);
+		if (cmd == SECCOMP_SET_MODE_FILTER) {
+		    return 0;
+		}
+	}
+#endif
+
+	/* gcc magic to attempt to just pass these args to prctl. we have to
+	 * guess about the number of args; the docs discuss calling conventions
+	 * up to 5, so let's try that?
+	 */
+	void *res = __builtin_apply((void (*)()) real_prctl, __builtin_apply_args(), sizeof(long) * 5);
+	__builtin_return(res);
+}
+
+/* unused.
+ */
+static int wrap_prctl(int option, va_list ap) {
+	(void) option;
+	(void) ap;
+	return -1;
+}
diff --git a/ports/linux/statx/guts/statx.c b/ports/linux/statx/guts/statx.c
new file mode 100644
index 0000000..42aebe5
--- /dev/null
+++ b/ports/linux/statx/guts/statx.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019 Linux Foundation
+ * Author: Richard Purdie
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * int
+ * statx(int dirfd, const char *path, int flags, unsigned int mask, struct statx *statxbuf) {
+ *	int rc = -1;
+ */
+	pseudo_msg_t *msg;
+	PSEUDO_STATBUF buf;
+	int save_errno;
+
+	rc = real_statx(dirfd, path, flags, mask, statxbuf);
+	save_errno = errno;
+	if (rc == -1) {
+		return rc;
+	}
+
+	buf.st_uid = statxbuf->stx_uid;
+	buf.st_gid = statxbuf->stx_gid;
+	buf.st_dev = makedev(statxbuf->stx_dev_major, statxbuf->stx_dev_minor);
+	buf.st_ino = statxbuf->stx_ino;
+	buf.st_mode = statxbuf->stx_mode;
+	buf.st_rdev = makedev(statxbuf->stx_rdev_major, statxbuf->stx_rdev_minor);
+	buf.st_nlink = statxbuf->stx_nlink;
+	msg = pseudo_client_op(OP_STAT, 0, -1, dirfd, path, &buf);
+	if (msg && msg->result == RESULT_SUCCEED) {
+		pseudo_debug(PDBGF_FILE, "statx(path %s), flags %o, stat rc %d, stat uid %o\n", path, flags, rc, statxbuf->stx_uid);
+		statxbuf->stx_uid = msg->uid;
+		statxbuf->stx_gid = msg->gid;
+		statxbuf->stx_mode = msg->mode;
+		statxbuf->stx_rdev_major = major(msg->rdev);
+		statxbuf->stx_rdev_minor = minor(msg->rdev);
+	} else {
+		pseudo_debug(PDBGF_FILE, "statx(path %s) failed, flags %o, stat rc %d, stat uid %o\n", path, flags, rc, statxbuf->stx_uid);
+	}
+	errno = save_errno;
+/*	return rc;
+ * }
+ */
diff --git a/ports/linux/statx/portdefs.h b/ports/linux/statx/portdefs.h
new file mode 100644
index 0000000..bf934dc
--- /dev/null
+++ b/ports/linux/statx/portdefs.h
@@ -0,0 +1,6 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
diff --git a/ports/linux/statx/wrapfuncs.in b/ports/linux/statx/wrapfuncs.in
new file mode 100644
index 0000000..168234f
--- /dev/null
+++ b/ports/linux/statx/wrapfuncs.in
@@ -0,0 +1 @@
+int statx(int dirfd, const char *path, int flags, unsigned int mask, struct statx *statxbuf);
diff --git a/ports/linux/subports b/ports/linux/subports
index a29044a..740ec83 100755
--- a/ports/linux/subports
+++ b/ports/linux/subports
@@ -29,11 +29,12 @@ fi
 if	$port_xattr; then
 	cat > dummy.c <<EOF
 #include <sys/types.h>
-#include <attr/xattr.h>
+#include <sys/xattr.h>
+#include <attr/attributes.h>
 int i;
 EOF
 	if ! ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
-		echo >&2 "Warning: Can't compile trivial program using <attr/xattr.h>".
+		echo >&2 "Warning: Can't compile trivial program using <attr/attributes.h>".
 		echo >&2 "         xattr support will require that header."
 	fi
 	echo "linux/xattr"
@@ -54,3 +55,13 @@ else
 fi
 rm -f dummy.c dummy.o
 
+cat > dummy.c <<EOF
+#define _GNU_SOURCE
+#include <sys/stat.h>
+struct statx x;
+EOF
+if ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
+	echo "linux/statx"
+fi
+rm -f dummy.c dummy.o
+
diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in
index c5ea7c3..80221fc 100644
--- a/ports/linux/wrapfuncs.in
+++ b/ports/linux/wrapfuncs.in
@@ -1,23 +1,24 @@
-int open(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW */
+int open(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
 char *get_current_dir_name(void);
 int __xstat(int ver, const char *path, struct stat *buf);
 int __lxstat(int ver, const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW */
 int __fxstat(int ver, int fd, struct stat *buf);
+int lchmod(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */
 int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */
 int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags);
-int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW */
-int __openat_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW */
+int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
+int __openat_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
 int mknod(const char *path, mode_t mode, dev_t dev); /* real_func=pseudo_mknod */
 int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev); /* real_func=pseudo_mknodat */
 int __xmknod(int ver, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */
 int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */
-int fcntl(int fd, int cmd, ...{struct flock *lock});
+int fcntl(int fd, int cmd, ...{struct flock *lock});  /* noignore_path=1 */
 # just so we know the inums of symlinks
 char *canonicalize_file_name(const char *filename);
 int eaccess(const char *path, int mode);
-int open64(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW */
-int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW */
-int __openat64_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW */
+int open64(const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
+int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
+int __openat64_2(int dirfd, const char *path, int flags); /* flags=flags&O_NOFOLLOW, noignore_path=1 */
 int creat64(const char *path, mode_t mode);
 int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat */
 int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW */
@@ -29,13 +30,14 @@ int __xstat64(int ver, const char *path, struct stat64 *buf);
 int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLINK_NOFOLLOW */
 int __fxstat64(int ver, int fd, struct stat64 *buf);
 int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags);
-FILE *fopen64(const char *path, const char *mode);
-int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag);
-FILE *freopen64(const char *path, const char *mode, FILE *stream);
+FILE *fopen64(const char *path, const char *mode); /* noignore_path=1 */
+int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); /* noignore_path=1 */
+FILE *freopen64(const char *path, const char *mode, FILE *stream);  /* noignore_path=1 */
 int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd);
 int glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob);
 int scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)());
 int truncate64(const char *path, off64_t length);
+int mkostemp64(char *template, int oflags); /* flags=AT_SYMLINK_NOFOLLOW */
 int mkstemp64(char *template); /* flags=AT_SYMLINK_NOFOLLOW */
 int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
 int setgroups(size_t size, const gid_t *list);
@@ -56,3 +58,4 @@ int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbuf
 int capset(cap_user_header_t hdrp, const cap_user_data_t datap); /* real_func=pseudo_capset */
 long syscall(long nr, ...); /* hand_wrapped=1 */
 int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); /* flags=AT_SYMLINK_NOFOLLOW */
+int prctl(int option, ...); /* hand_wrapped=1 */
diff --git a/ports/linux/xattr/portdefs.h b/ports/linux/xattr/portdefs.h
index 56cd3ca..068d39a 100644
--- a/ports/linux/xattr/portdefs.h
+++ b/ports/linux/xattr/portdefs.h
@@ -2,5 +2,6 @@
  * SPDX-License-Identifier: LGPL-2.1-only
  *
  */
-#include <attr/xattr.h>
+#include <sys/xattr.h>
+#include <attr/attributes.h>
 #include <stdint.h>
diff --git a/ports/linux/xattr/pseudo_wrappers.c b/ports/linux/xattr/pseudo_wrappers.c
index 590af30..0b65920 100644
--- a/ports/linux/xattr/pseudo_wrappers.c
+++ b/ports/linux/xattr/pseudo_wrappers.c
@@ -134,7 +134,7 @@ static ssize_t shared_getxattr(const char *path, int fd, const char *name, void
 	pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n",
 		path ? path : "<no path>", fd, name);
 	pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name);
-	if (result->result != RESULT_SUCCEED) {
+	if (!result || result->result != RESULT_SUCCEED) {
 		errno = ENOATTR;
 		return -1;
 	}
@@ -197,7 +197,7 @@ static int shared_setxattr(const char *path, int fd, const char *name, const voi
 			mode |= get_special_bits(path, fd);
 			pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n",
 				mode, extra);
-			buf.st_mode = mode;
+
 			/* we want to actually issue a corresponding chmod,
 			 * as well, or else the file ends up 0600 on the
 			 * host. Using the slightly-less-efficient wrap_chmod
@@ -254,7 +254,7 @@ static int shared_setxattr(const char *path, int fd, const char *name, const voi
 static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size) {
 	RC_AND_BUF
 	pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf);
-	if (result->result != RESULT_SUCCEED) {
+	if (!result || result->result != RESULT_SUCCEED) {
 		pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n");
 		errno = ENOATTR;
 		return -1;
@@ -276,7 +276,7 @@ static int shared_removexattr(const char *path, int fd, const char *name) {
 	RC_AND_BUF
 	pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name);
 
-	if (result->result != RESULT_SUCCEED) {
+	if (!result || result->result != RESULT_SUCCEED) {
 		/* docs say ENOATTR, but I don't have one */
 		errno = ENOENT;
 		return -1;
diff --git a/ports/linux/xattr/wrapfuncs.in b/ports/linux/xattr/wrapfuncs.in
index c37f78a..09eba23 100644
--- a/ports/linux/xattr/wrapfuncs.in
+++ b/ports/linux/xattr/wrapfuncs.in
@@ -1,12 +1,12 @@
-ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0 */
-ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */
-ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size);
-int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0 */
-int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW */
-int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags);
-ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0 */
-ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */
-ssize_t flistxattr(int filedes, char *list, size_t size);
-int removexattr(const char *path, const char *name); /* flags=0 */
-int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW */
-int fremovexattr(int filedes, const char *name);
+ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+ssize_t flistxattr(int filedes, char *list, size_t size); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int removexattr(const char *path, const char *name); /* flags=0, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW, version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
+int fremovexattr(int filedes, const char *name); /* version="GLIBC_2.3", version-aarch64="GLIBC_2.17" */
diff --git a/ports/unix/guts/fchmodat.c b/ports/unix/guts/fchmodat.c
index 55dbd35..d1ac7bb 100644
--- a/ports/unix/guts/fchmodat.c
+++ b/ports/unix/guts/fchmodat.c
@@ -11,16 +11,16 @@
 	PSEUDO_STATBUF buf;
 	int save_errno = errno;
 
-	if (flags & AT_SYMLINK_NOFOLLOW) {
-		errno = ENOTSUP;
-		return -1;
-	}
 #ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
 	if (dirfd != AT_FDCWD) {
 		errno = ENOSYS;
 		return -1;
 	}
-	rc = base_stat(path, &buf);
+	if (flags & AT_SYMLINK_NOFOLLOW) {
+	    rc = base_lstat(path, &buf);
+	} else {
+	    rc = base_stat(path, &buf);
+	}
 #else
 	rc = base_fstatat(dirfd, path, &buf, flags);
 #endif
diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c
index 381f9d0..7d8dff4 100644
--- a/ports/unix/guts/linkat.c
+++ b/ports/unix/guts/linkat.c
@@ -116,6 +116,7 @@
 	 * if the thing linked is a symlink.
 	 */
 	pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf);
+	pseudo_client_linked_paths(oldpath, newpath);
 
 	errno = save_errno;
 
diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c
index 085d2cb..8d8118b 100644
--- a/ports/unix/guts/realpath.c
+++ b/ports/unix/guts/realpath.c
@@ -14,7 +14,14 @@
 		errno = ENAMETOOLONG;
 		return NULL;
 	}
-	if ((len = strlen(rname)) >= pseudo_sys_path_max()) {
+		len = strlen(rname);
+		char *ep = rname + len - 1;
+		while (ep > rname && *ep == '/') {
+			--len;
+			*(ep--) = '\0';
+		}
+
+		if (len >= pseudo_sys_path_max()) {
 		errno = ENAMETOOLONG;
 		return NULL;
 	}
diff --git a/ports/unix/guts/rename.c b/ports/unix/guts/rename.c
index 5073c71..7e4a499 100644
--- a/ports/unix/guts/rename.c
+++ b/ports/unix/guts/rename.c
@@ -29,6 +29,14 @@
 	newrc = base_lstat(newpath, &newbuf);
 	oldrc = base_lstat(oldpath, &oldbuf);
 
+	/* nothing to do for a "rename" of a link to itself */
+	if (newrc != -1 && oldrc != -1 &&
+	    newbuf.st_dev == oldbuf.st_dev &&
+	    newbuf.st_ino == oldbuf.st_ino) {
+		pseudo_debug(PDBGF_OP, "rename: paths are the same\n");
+		return real_rename(oldpath, newpath);
+        }
+
 	errno = save_errno;
 
 	/* newpath must be removed. */
@@ -58,12 +66,6 @@
 		return rc;
 	}
 	save_errno = errno;
-	/* nothing to do for a "rename" of a link to itself */
-	if (newrc != -1 && oldrc != -1 &&
-	    newbuf.st_dev == oldbuf.st_dev &&
-	    newbuf.st_ino == oldbuf.st_ino) {
-		return rc;
-        }
 
 	/* rename(3) is not mv(1).  rename(file, dir) fails; you must provide
 	 * the corrected path yourself.  You can rename over a directory only
diff --git a/ports/unix/guts/renameat.c b/ports/unix/guts/renameat.c
index 735a60a..8064818 100644
--- a/ports/unix/guts/renameat.c
+++ b/ports/unix/guts/renameat.c
@@ -41,6 +41,14 @@
 	newrc = base_fstatat(newdirfd, newpath, &newbuf, AT_SYMLINK_NOFOLLOW);
 #endif
 
+	/* nothing to do for a "rename" of a link to itself */
+	if (newrc != -1 && oldrc != -1 &&
+	    newbuf.st_dev == oldbuf.st_dev &&
+	    newbuf.st_ino == oldbuf.st_ino) {
+		pseudo_debug(PDBGF_OP, "renameat: paths are the same\n");
+		return real_renameat(olddirfd, oldpath, newdirfd, newpath);
+        }
+
 	errno = save_errno;
 
 	/* newpath must be removed. */
@@ -71,12 +79,6 @@
 		return rc;
 	}
 	save_errno = errno;
-	/* nothing to do for a "rename" of a link to itself */
-	if (newrc != -1 && oldrc != -1 &&
-	    newbuf.st_dev == oldbuf.st_dev &&
-	    newbuf.st_ino == oldbuf.st_ino) {
-		return rc;
-        }
 
 	/* rename(3) is not mv(1).  rename(file, dir) fails; you must provide
 	 * the corrected path yourself.  You can rename over a directory only
diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in
index 3910fae..bd2706f 100644
--- a/ports/unix/wrapfuncs.in
+++ b/ports/unix/wrapfuncs.in
@@ -1,14 +1,14 @@
 int creat(const char *path, mode_t mode);
 char *getcwd(char *buf, size_t size);
 char *getwd(char *buf);
-int close(int fd);
+int close(int fd);  /* noignore_path=1 */
 int fchmod(int fd, mode_t mode);
 int fchown(int fd, uid_t owner, gid_t group);
 int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */
-int dup2(int oldfd, int newfd);
-int dup(int fd);
-int chdir(const char *path);
-int fchdir(int dirfd);
+int dup2(int oldfd, int newfd); /* noignore_path=1 */
+int dup(int fd); /* noignore_path=1 */
+int chdir(const char *path); /* noignore_path=1 */
+int fchdir(int dirfd); /* noignore_path=1 */
 int access(const char *path, int mode);
 FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */
 int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd);
@@ -20,18 +20,18 @@ char *mktemp(char *template);
 long pathconf(const char *path, int name);
 char *realpath(const char *name, char *resolved_name); /* version="GLIBC_2.3" */
 int remove(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */
-DIR *opendir(const char *path);
-int closedir(DIR *dirp);
+DIR *opendir(const char *path); /* noignore_path=1 */
+int closedir(DIR *dirp); /* noignore_path=1 */
 char *tempnam(const char *template, const char *pfx);
 char *tmpnam(char *s);
 int truncate(const char *path, off_t length);
 int utime(const char *path, const struct utimbuf *buf);
 int utimes(const char *path, const struct timeval *times);
 # needed because libc stdio does horrible things with inline asm syscalls
-FILE *fopen(const char *path, const char *mode);
-int fclose(FILE *fp);
-FILE *freopen(const char *path, const char *mode, FILE *stream);
-int chroot(const char *path);
+FILE *fopen(const char *path, const char *mode); /* noignore_path=1 */
+int fclose(FILE *fp);  /* noignore_path=1 */
+FILE *freopen(const char *path, const char *mode, FILE *stream); /* noignore_path=1 */
+int chroot(const char *path); /* noignore_path=1 */
 int acct(const char *path);
 int chmod(const char *path, mode_t mode);
 int chown(const char *path, uid_t owner, gid_t group);
diff --git a/pseudo.c b/pseudo.c
index 0f5850e..f2e2f87 100644
--- a/pseudo.c
+++ b/pseudo.c
@@ -245,10 +245,12 @@ main(int argc, char *argv[]) {
 	/* Options are processed, preserve them... */
 	pseudo_set_value("PSEUDO_OPTS", opts);
 
-	if (!pseudo_get_prefix(argv[0])) {
+	s = pseudo_get_prefix(argv[0]);
+	if (!s) {
 		pseudo_diag("Can't figure out prefix.  Set PSEUDO_PREFIX or invoke with full path.\n");
 		exit(PSEUDO_EXIT_PSEUDO_PREFIX);
 	}
+	free(s);
 
 	/* move database */
 	if (opt_m || opt_M) {
@@ -695,16 +697,15 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **respon
 						msg->path);
 					pdb_did_unlink_file(path_by_ino, &by_ino, by_ino.deleting);
 				} else {
-					int flags = 0;
-					if (msg->nlink > 1) {
-						flags = PDBGF_FILE | PDBGF_VERBOSE;
-					}
-					pseudo_debug(flags, "path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n",
+					pseudo_diag("path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n",
 						msg->nlink,
 						msg->nlink == 1 ? "" : "s",
 						(unsigned long long) msg_header.ino,
 						path_by_ino ? path_by_ino : "no path",
 						msg->path);
+					found_ino = 0;
+					msg->result = RESULT_ABORT;
+					goto op_exit;
 				}
 			}
 		} else {
@@ -1024,6 +1025,24 @@ pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **respon
 		break;
 	}
 
+	switch (msg->op) {
+	case OP_FCHOWN:		/* FALLTHROUGH */
+	case OP_FCHMOD:		/* FALLTHROUGH */
+	case OP_FSTAT:
+		if (!found_path && !found_ino && (msg->nlink == 0)) {
+			/* If nlink is 0 for an fchown/fchmod/fstat, we probably have an fd which is 
+			 * unlinked and we don't want to do inode/path matching against it. Marking it 
+ 			 * as may unlink gives the right hints in the database to ensure we
+			 * handle correctly whilst maintaining the permissions whilst the 
+			 * file exists for the fd.  */
+			pdb_may_unlink_file(msg, msg->client);
+		}
+		break;
+	default:
+		break;
+	}
+
+op_exit:
 	/* in the case of an exact match, we just used the pointer
 	 * rather than allocating space.
 	 */
@@ -1087,9 +1106,15 @@ pseudo_db_check(int fix) {
 			int fixup_needed = 0;
 			pseudo_debug(PDBGF_DB, "Checking <%s>\n", m->path);
 			if (lstat(m->path, &buf)) {
-				errors = EXIT_FAILURE;
-				pseudo_diag("can't stat <%s>\n", m->path);
-				continue;
+				if (!fix) {
+					pseudo_diag("can't stat <%s>\n", m->path);
+					errors = EXIT_FAILURE;
+					continue;
+				} else {
+					pseudo_debug(PDBGF_DB, "can't stat <%s>\n", m->path);
+					fixup_needed = 2;
+					goto do_fixup;
+				}
 			}
 			/* can't check for device type mismatches, uid/gid, or
 			 * permissions, because those are the very things we
@@ -1125,6 +1150,7 @@ pseudo_db_check(int fix) {
 					S_ISDIR(m->mode));
 				fixup_needed = 2;
 			}
+			do_fixup:
 			if (fixup_needed) {
 				/* in fixup mode, either delete (mismatches) or
 				 * correct (dev/ino).
diff --git a/pseudo_client.c b/pseudo_client.c
index 478e450..f846d54 100644
--- a/pseudo_client.c
+++ b/pseudo_client.c
@@ -70,6 +70,8 @@ int pseudo_umask = 022;
 
 static char **fd_paths = NULL;
 static int nfds = 0;
+static char **linked_fd_paths = NULL;
+static int linked_nfds = 0;
 static const char **passwd_paths = NULL;
 static int npasswd_paths = 0;
 #ifdef PSEUDO_PROFILING
@@ -429,6 +431,7 @@ pseudo_profile_report(void) {
 void
 pseudo_init_client(void) {
 	char *env;
+	int need_free = 0;
 
 	pseudo_antimagic();
 	pseudo_new_pid();
@@ -448,9 +451,11 @@ pseudo_init_client(void) {
 	 * or it may have gone away, in which case we'd enable
 	 * pseudo (and cause it to reinit the defaults).
 	 */
+	need_free = 0;
 	env = getenv("PSEUDO_DISABLED");
 	if (!env) {
 		env = pseudo_get_value("PSEUDO_DISABLED");
+		need_free = 1;
 	}
 	if (env) {
 		int actually_disabled = 1;
@@ -485,15 +490,19 @@ pseudo_init_client(void) {
 	} else {
 		pseudo_set_value("PSEUDO_DISABLED", "0");
 	}
+	if (need_free)
+		free(env);
 
 	/* ALLOW_FSYNC is here because some crazy hosts will otherwise
 	 * report incorrect values for st_size/st_blocks. I can sort of
 	 * understand st_blocks, but bogus values for st_size? Not cool,
 	 * dudes, not cool.
 	 */
+	need_free = 0;
 	env = getenv("PSEUDO_ALLOW_FSYNC");
 	if (!env) {
 		env = pseudo_get_value("PSEUDO_ALLOW_FSYNC");
+		need_free = 1;
 	} else {
 		pseudo_set_value("PSEUDO_ALLOW_FSYNC", env);
 	}
@@ -502,6 +511,8 @@ pseudo_init_client(void) {
 	} else {
 		pseudo_allow_fsync = 0;
 	}
+	if (need_free)
+		free(env);
 
 	/* in child processes, PSEUDO_UNLOAD may become set to
 	 * some truthy value, in which case we're being asked to
@@ -822,6 +833,8 @@ pseudo_client_chroot(const char *path) {
 	}
 	memcpy(pseudo_chroot, path, pseudo_chroot_len + 1);
 	pseudo_set_value("PSEUDO_CHROOT", pseudo_chroot);
+	/* Rebuild passwd paths since we've done a chroot */
+	build_passwd_paths();
 	return 0;
 }
 
@@ -889,32 +902,73 @@ fd_path(int fd) {
 }
 
 static void
-pseudo_client_path(int fd, const char *path) {
+pseudo_client_path_set(int fd, const char *path, char ***patharray, int *len) {
 	if (fd < 0)
 		return;
 
-	if (fd >= nfds) {
+	if (fd >= *len) {
 		int i;
 		pseudo_debug(PDBGF_CLIENT, "expanding fds from %d to %d\n",
-			nfds, fd + 1);
-		fd_paths = realloc(fd_paths, (fd + 1) * sizeof(char *));
-		for (i = nfds; i < fd + 1; ++i)
-			fd_paths[i] = 0;
-		nfds = fd + 1;
+			*len, fd + 1);
+		(*patharray) = realloc((*patharray), (fd + 1) * sizeof(char *));
+		for (i = *len; i < fd + 1; ++i)
+			(*patharray)[i] = 0;
+		*len = fd + 1;
 	} else {
-		if (fd_paths[fd]) {
+		if ((*patharray)[fd]) {
 			pseudo_debug(PDBGF_CLIENT, "reopening fd %d [%s] -- didn't see close\n",
-				fd, fd_paths[fd]);
+				fd, (*patharray)[fd]);
 		}
 		/* yes, it is safe to free null pointers. yay for C89 */
-		free(fd_paths[fd]);
-		fd_paths[fd] = 0;
+		free((*patharray)[fd]);
+		(*patharray)[fd] = 0;
 	}
 	if (path) {
-		fd_paths[fd] = strdup(path);
+		(*patharray)[fd] = strdup(path);
+	}
+}
+
+static void
+pseudo_client_path(int fd, const char *path) {
+	pseudo_client_path_set(fd, path, &fd_paths, &nfds);
+}
+
+void
+pseudo_client_linked_paths(const char *oldpath, const char *newpath) {
+	int fd;
+	for (fd = 3; fd < nfds; ++fd) {
+		if (fd_paths[fd] && !strcmp(oldpath, fd_paths[fd])) {
+			pseudo_client_path_set(fd, newpath, &linked_fd_paths, &linked_nfds);
+		}
+	}
+}
+
+static void
+pseudo_client_rename_path(const char *oldpath, const char *newpath) {
+	int fd;
+	for (fd = 3; fd < nfds; ++fd) {
+		if (fd_paths[fd] && !strcmp(oldpath, fd_paths[fd])) {
+                        pseudo_client_path(fd, newpath);
+		}
+	}
+	for (fd = 0; fd < linked_nfds; ++fd) {
+		if (linked_fd_paths[fd] && fd_paths[fd] && !strcmp(oldpath, linked_fd_paths[fd])) {
+			pseudo_client_path_set(fd, newpath, &linked_fd_paths, &linked_nfds);
+		}
+	}
+}
+
+static void
+pseudo_client_unlinked_path(const char *path) {
+	int fd;
+	for (fd = 0; fd < linked_nfds; ++fd) {
+		if (linked_fd_paths[fd] && fd_paths[fd] && !strcmp(path, fd_paths[fd])) {
+			pseudo_client_path(fd, linked_fd_paths[fd]);
+		}
 	}
 }
 
+
 static void
 pseudo_client_close(int fd) {
 	if (fd < 0 || fd >= nfds)
@@ -922,6 +976,11 @@ pseudo_client_close(int fd) {
 
 	free(fd_paths[fd]);
 	fd_paths[fd] = 0;
+
+	if (fd < linked_nfds) {
+		free(linked_fd_paths[fd]);
+		linked_fd_paths[fd] = 0;
+	}
 }
 
 /* spawn server */
@@ -1271,7 +1330,7 @@ pseudo_client_setup(void) {
 	}
 }
 
-#define PSEUDO_RETRIES 20
+#define PSEUDO_RETRIES 250
 static pseudo_msg_t *
 pseudo_client_request(pseudo_msg_t *msg, size_t len, const char *path) {
 	pseudo_msg_t *response = 0;
@@ -1436,8 +1495,12 @@ base_path(int dirfd, const char *path, int leave_last) {
 
 	if (!path)
 		return NULL;
-	if (!*path)
+
+	if (!*path) {
+		if (dirfd != -1 && dirfd != AT_FDCWD)
+			return fd_path(dirfd);
 		return "";
+	}
 
 	if (path[0] != '/') {
 		if (dirfd != -1 && dirfd != AT_FDCWD) {
@@ -1482,6 +1545,47 @@ base_path(int dirfd, const char *path, int leave_last) {
 	return newpath;
 }
 
+int pseudo_client_ignore_fd(int fd) {
+	if (fd >= 0 && fd <= nfds)
+		return pseudo_client_ignore_path(fd_path(fd));
+	return 0;
+}
+
+int pseudo_client_ignore_path_chroot(const char *path, int ignore_chroot) {
+	char *env;
+
+	if (!path)
+		return 0;
+
+	if (ignore_chroot && pseudo_chroot && strncmp(path, pseudo_chroot, pseudo_chroot_len) == 0)
+		return 0;
+
+	env = pseudo_get_value("PSEUDO_IGNORE_PATHS");
+	if (!env)
+		return 0;
+
+	int ret = 0;
+	char *p = env;
+	while (p) {
+		char *next = strchr(p, ',');
+		if (next)
+			*next++ = '\0';
+		if (*p && !strncmp(path, p, strlen(p))) {
+			pseudo_debug(PDBGF_PATH | PDBGF_VERBOSE, "ignoring path: '%s'\n", path);
+			ret = 1;
+			break;
+		}
+		p = next;
+	}
+	free(env);
+
+	return ret;
+}
+
+int pseudo_client_ignore_path(const char *path) {
+	return pseudo_client_ignore_path_chroot(path, 1);
+}
+
 pseudo_msg_t *
 pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...) {
 	pseudo_msg_t *result = 0;
@@ -1522,6 +1626,16 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 		}
 	}
 
+	if (op != OP_CHROOT && op != OP_CHDIR && op != OP_CLOSE && op != OP_DUP
+			&& pseudo_client_ignore_path_chroot(path, 0)) {
+		if (op == OP_OPEN) {
+			pseudo_client_path(fd, path);
+		}
+		/* reenable wrappers */
+		pseudo_magic();
+		return result;
+	}
+
 #ifdef PSEUDO_XATTRDB
 	if (buf) {
 		struct stat64 bufcopy = *buf;
@@ -1772,7 +1886,8 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 		break;
 	case OP_OPEN:
 		pseudo_client_path(fd, path);
-	case OP_EXEC: /* fallthrough */
+		/* fallthrough */
+	case OP_EXEC:
 		do_request = pseudo_client_logging;
 		break;
 	case OP_CLOSE:
@@ -1813,6 +1928,12 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 			dirfd);
 		pseudo_client_path(dirfd, fd_path(fd));
 		break;
+	case OP_UNLINK:
+	case OP_DID_UNLINK:
+		if (path)
+			pseudo_client_unlinked_path(path);
+		do_request = 1;
+		break;
 	/* operations for which we should use the magic uid/gid */
 	case OP_CHMOD:
 	case OP_CREAT:
@@ -1833,10 +1954,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 	case OP_FCHOWN:
 	case OP_FSTAT:
 	case OP_LINK:
-	case OP_RENAME:
 	case OP_STAT:
-	case OP_UNLINK:
-	case OP_DID_UNLINK:
 	case OP_CANCEL_UNLINK:
 	case OP_MAY_UNLINK:
 	case OP_GET_XATTR:
@@ -1845,6 +1963,10 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 	case OP_REMOVE_XATTR:
 		do_request = 1;
 		break;
+	case OP_RENAME:
+		pseudo_client_rename_path(path_extra_1, path);
+		do_request = 1;
+		break;
 	default:
 		pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op));
 		break;
@@ -1876,6 +1998,12 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path
 #endif
 		if (result) {
 			pseudo_debug(PDBGF_OP, "(%d) %s", getpid(), pseudo_res_name(result->result));
+			if (result->result == RESULT_ABORT) {
+				char *local_state_dir = pseudo_get_value("PSEUDO_LOCALSTATEDIR");
+				pseudo_diag("abort()ing pseudo client by server request. See https://wiki.yoctoproject.org/wiki/Pseudo_Abort for more details on this.\n"
+					"Check logfile: %s/%s\n", local_state_dir, PSEUDO_LOGFILE);
+				abort();
+			}
 			if (op == OP_STAT || op == OP_FSTAT) {
 				pseudo_debug(PDBGF_OP, " mode 0%o uid %d:%d",
 					(int) result->mode,
diff --git a/pseudo_client.h b/pseudo_client.h
index 457b095..d7944ce 100644
--- a/pseudo_client.h
+++ b/pseudo_client.h
@@ -7,6 +7,9 @@
  *
  */
 extern pseudo_msg_t *pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...);
+extern int pseudo_client_ignore_path(const char *path);
+extern int pseudo_client_ignore_fd(int fd);
+extern void pseudo_client_linked_paths(const char *oldpath, const char *newpath);
 #if PSEUDO_STATBUF_64
 #define base_lstat real_lstat64
 #define base_fstat real_fstat64
diff --git a/pseudo_db.c b/pseudo_db.c
index 92e4f50..ebf6e08 100644
--- a/pseudo_db.c
+++ b/pseudo_db.c
@@ -158,11 +158,24 @@ static struct sql_index {
 
 static char *file_pragmas[] = {
 	"PRAGMA legacy_file_format = OFF;",
-	"PRAGMA journal_mode = OFF;",
+#ifdef USE_MEMORY_DB
 	/* the default page size produces painfully bad behavior
 	 * for memory databases with some versions of sqlite.
 	 */
 	"PRAGMA page_size = 8192;",
+	"PRAGMA journal_mode = OFF;",
+#else
+        /* Use WAL mode when using the on-disk database. If user care about
+         * performance, they can use the in-memory database, but if they care
+         * more about resilience, they can disable it and WAL mode will prevent
+         * corruption of the on-disk database (for a slight performance
+         * penalty). Note that the database still keeps synchronous to OFF,
+         * meaning its resilient to the pseudo process crashing or being killed
+         * unexpectedly, but not to the OS crashing and losing buffered disk
+         * state
+         */
+	"PRAGMA journal_mode = WAL;",
+#endif
 	"PRAGMA locking_mode = EXCLUSIVE;",
 	/* Setting this to NORMAL makes pseudo noticably slower
 	 * than fakeroot, but is perhaps more secure.  However,
diff --git a/pseudo_ipc.h b/pseudo_ipc.h
index caeae5c..d945257 100644
--- a/pseudo_ipc.h
+++ b/pseudo_ipc.h
@@ -29,7 +29,7 @@ typedef struct {
 	char path[];
 } pseudo_msg_t;
 
-enum {
+typedef enum {
 	PSA_EXEC = 1,
 	PSA_WRITE = (PSA_EXEC << 1),
 	PSA_READ = (PSA_WRITE << 1),
diff --git a/pseudo_server.c b/pseudo_server.c
index 898aab4..84cef05 100644
--- a/pseudo_server.c
+++ b/pseudo_server.c
@@ -792,6 +792,7 @@ pseudo_server_loop(void) {
 	struct sigaction eat_usr2 = {
 		.sa_handler = set_do_list_clients
 	};
+	int hitmaxfiles;
 
 	clients = malloc(16 * sizeof(*clients));
 
@@ -810,6 +811,7 @@ pseudo_server_loop(void) {
 	active_clients = 1;
 	max_clients = 16;
 	highest_client = 0;
+	hitmaxfiles = 0;
 
 	pseudo_debug(PDBGF_SERVER, "server loop started.\n");
 	if (listen_fd < 0) {
@@ -868,10 +870,15 @@ pseudo_server_loop(void) {
 					} else {
 						serve_client(i);
 					}
+				} else if (hitmaxfiles) {
+					/* Only close one per loop iteration in the interests of caution */
+					close_client(i);
+					hitmaxfiles = 0;
 				}
 				if (die_forcefully)
 					break;
 			}
+			hitmaxfiles = 0;
 			if (!die_forcefully && 
 			    (FD_ISSET(clients[0].fd, &events) ||
 			     FD_ISSET(clients[0].fd, &reads))) {
@@ -893,6 +900,9 @@ pseudo_server_loop(void) {
 					 */
 					pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT;
 					die_peacefully = 0;
+				} else if (errno == EMFILE) {
+					hitmaxfiles = 1;
+					pseudo_debug(PDBGF_SERVER, "Hit max open files, dropping a client.\n");
 				}
 			}
 			pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients);
diff --git a/pseudo_util.c b/pseudo_util.c
index c867ed6..b6980c2 100644
--- a/pseudo_util.c
+++ b/pseudo_util.c
@@ -43,6 +43,7 @@ static struct pseudo_variables pseudo_env[] = {
 	{ "PSEUDO_BINDIR", 13, NULL },
 	{ "PSEUDO_LIBDIR", 13, NULL },
 	{ "PSEUDO_LOCALSTATEDIR", 20, NULL },
+	{ "PSEUDO_IGNORE_PATHS", 19, NULL },
 	{ "PSEUDO_PASSWD", 13, NULL },
 	{ "PSEUDO_CHROOT", 13, NULL },
 	{ "PSEUDO_UIDS", 11, NULL },
@@ -158,7 +159,7 @@ pseudo_get_value(const char *key) {
 	if (pseudo_util_initted == -1)
 		pseudo_init_util();
 
-	for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++)
+	for (i = 0; pseudo_env[i].key && strcmp(pseudo_env[i].key, key); i++)
 		;
 
 	/* Check if the environment has it and we don't ...
@@ -187,7 +188,7 @@ pseudo_set_value(const char *key, const char *value) {
 	if (pseudo_util_initted == -1)
 		pseudo_init_util();
 
-	for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++)
+	for (i = 0; pseudo_env[i].key && strcmp(pseudo_env[i].key, key); i++)
 		;
 
 	if (pseudo_env[i].key) {
@@ -966,6 +967,7 @@ pseudo_setupenv() {
 		}
 		snprintf(newenv, len, "%s:%s64", libdir_path, libdir_path);
 		SETENV(PRELINK_PATH, newenv, 1);
+		free(newenv);
 	} else if (!strstr(ld_library_path, libdir_path)) {
 		size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
 		char *newenv = malloc(len);
@@ -974,6 +976,7 @@ pseudo_setupenv() {
 		}
 		snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path);
 		SETENV(PRELINK_PATH, newenv, 1);
+		free(newenv);
 	} else {
 		/* nothing to do, ld_library_path exists and contains
 		 * our preferred path */
diff --git a/templates/wrapfuncs.c b/templates/wrapfuncs.c
index 3859183..93bb671 100644
--- a/templates/wrapfuncs.c
+++ b/templates/wrapfuncs.c
@@ -60,9 +60,15 @@ ${maybe_async_skip}
 		${rc_assign} (*real_${name})(${call_args});
 	} else {
 		${fix_paths}
-		/* exec*() use this to restore the sig mask */
-		pseudo_saved_sigmask = saved;
-		${rc_assign} wrap_$name(${call_args});
+		if (${ignore_paths}) {
+			/* call the real syscall */
+			pseudo_debug(PDBGF_SYSCALL, "${name} ignored path, calling real syscall.\n");
+			${rc_assign} (*real_${name})(${call_args});
+		} else {
+			/* exec*() use this to restore the sig mask */
+			pseudo_saved_sigmask = saved;
+			${rc_assign} wrap_$name(${call_args});
+		}
 	}
 	${variadic_end}
 	save_errno = errno;
diff --git a/test/test-acl.sh b/test/test-acl.sh
new file mode 100755
index 0000000..fb7d5ec
--- /dev/null
+++ b/test/test-acl.sh
@@ -0,0 +1,188 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+# Return vals: 2 - Unable to run ACL commands, assertion failure
+#              1 - Invalid return value
+#              0 - Pass
+
+# NOTE: these test exclusively test setfacl -m
+
+set -u
+
+check_owner () {
+    local file="$1"
+    local expected="$2"
+    local msg="$3"
+    local actual=$(stat -c "%U" "$file")
+    if [ "$actual" != "$expected" ]
+    then
+        echo "$msg" "Fail, '$file' unexpected owner '$actual'"
+        exit 2
+    fi
+}
+
+check_group () {
+    local file="$1"
+    local expected="$2"
+    local msg="$3"
+    local actual=$(stat -c "%G" "$file")
+    if [ "$actual" != "$expected" ]
+    then
+        echo "$msg" "Fail, '$file' unexpected group '$actual'"
+        exit 2
+    fi
+}
+
+check_acl_contains () {
+    local file="$1"
+    local acl="$2"
+    local msg="$3"
+    IFS=',' read -ra acls <<< "$acl"
+    for pattern in "${acls[@]}"; do
+        result=$(getfacl -c "$file" | grep -o "^$pattern")
+        if [ "$result" != "$pattern" ]
+        then
+            echo "$msg" "Fail, did not find desired acl '$pattern' in '$file'"
+            exit 2
+        fi
+    done
+}
+
+check_acl_minimal () {
+    local file="$1"
+    local msg="${2:-''}"
+    local acls
+    acls=$(getfacl -c "${file}" | grep -v "::")
+    if [ -n "$acls" ]
+    then
+        echo "$msg" "Fail, '$file' unexpected getfacl result '$acls'"
+        exit 1
+    fi
+}
+
+test_modify_once () {
+    local file="$1"
+    local acl="$2"
+    local msg="${3:-''}"
+    # ensure that file is pristine
+    check_acl_minimal "$file" "$msg precondition:"
+    check_owner "$file" root "$msg precondition:"
+    check_group "$file" root "$msg precondition:"
+    if ! setfacl -m "$acl" "$file"
+    then
+        echo "$msg"  "Fail, unable to call setfacl"
+        exit 2
+    fi
+    check_acl_contains "$file" "$acl" "$msg: acl not set:"
+    check_owner "$file" root "$msg owner corrupted:"
+    check_group "$file" root "$msg group corrupted:"
+}
+
+
+trap "rm -rf testdir" EXIT
+mkdir testdir || exit 1
+
+
+# user
+touch testdir/f1 || exit 1
+mkdir testdir/d1 || exit 1
+# regular file
+test_modify_once testdir/f1 "user:root:r" "$LINENO:"
+# directory
+test_modify_once testdir/d1 "user:root:r" "$LINENO:"
+rm -rf testdir/f1 testdir/d1
+
+#group
+rm -rf testdir/f1 testdir/d1
+touch testdir/f1 || exit 1
+mkdir testdir/d1 || exit 1
+# regular file
+test_modify_once testdir/f1 "group:root:r" "$LINENO:"
+# directory
+test_modify_once testdir/d1 "group:root:r" "$LINENO:"
+rm -rf testdir/f1 testdir/d1
+
+# multiple users
+touch testdir/f1 || exit 1
+mkdir testdir/d1 || exit 1
+# regular file
+test_modify_once testdir/f1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:"
+# directory
+test_modify_once testdir/d1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:"
+rm -rf testdir/f1 testdir/d1
+
+
+# setfacl default acls
+mkdir testdir/d1 || exit 1
+test_modify_once testdir/d1 "default:user:root:r,user:root:r" "$LINENO:"
+rm -rf testdir/d1
+
+
+# multiple calls to setfacl -m on same file
+touch testdir/f1 || exit 1
+mkdir testdir/d1 || exit 1
+check_owner testdir/f1 root "$LINENO: precondition:"
+check_group testdir/f1 root "$LINENO: precondition:"
+check_acl_minimal testdir/f1 "$LINENO: precondition:"
+
+acl1="user:root:r"
+acl2="user:bin:rw"
+
+if ! setfacl -m "$acl1" testdir/f1 # first setfacl
+then
+    echo "$LINENO:"  "Fail, unable to call setfacl"
+    exit 2
+fi
+check_acl_contains testdir/f1 "$acl1" "$LINENO: acl1 not set:"
+check_owner testdir/f1 root "$LINENO: owner corrupted:"
+check_group testdir/f1 root "$LINENO: group corrupted:"
+
+if ! setfacl -m "$acl2" testdir/f1 # second setfacl
+then
+    echo "$LINENO:"  "Fail, unable to call setfacl"
+    exit 2
+fi
+
+check_acl_contains testdir/f1 "$acl1" "$LINENO: acl1 not set:"
+check_acl_contains testdir/f1 "$acl2" "$LINENO: acl2 not set:"
+check_owner testdir/f1 root "$LINENO: owner corrupted:"
+check_group testdir/f1 root "$LINENO: group corrupted:"
+rm -rf testdir/f1 testdir/d1
+
+# setfacl recursive
+test_modify_recursive () {
+    local root_dir="$1"
+    local acl="$2"
+    local msg="${3:-''}"
+
+    find "$root_dir" | while read -r file; do
+        check_owner "$file" root "$msg precondition:"
+        check_group "$file" root "$msg precondition:"
+        check_acl_minimal "$file" "$msg precondition:"
+    done
+    if ! setfacl -R -m "$acl" "$root_dir"
+    then
+        echo "$msg"  "Fail, unable to call setfacl"
+        exit 2
+    fi
+    find "$root_dir" | while read -r file; do
+        check_owner "$file" root "$msg owner corrupted:"
+        check_group "$file" root "$msg group corrupted:"
+        check_acl_contains "$file" "$acl" "$msg acl not set:"
+    done
+}
+
+mkdir -p testdir/d1/d2 || exit 1
+touch testdir/d1/d2/f1 || exit 1
+test_modify_recursive testdir/d1 "user:root:r,group:root:r,user:bin:rw" "$LINENO:"
+rm -rf testdir/d1
+
+mkdir -p testdir/d1/d2 || exit 1
+mkdir -p testdir/d1/d3 || exit 1
+test_modify_recursive testdir/d1 "default:user:root:rwx,user:root:r,group:root:r,user:bin:rw" "$LINENO:"
+rm -rf testdir/d1
+
+#echo "Passed."
+exit 0
diff --git a/test/test-rename-fstat.c b/test/test-rename-fstat.c
new file mode 100644
index 0000000..fb47c05
--- /dev/null
+++ b/test/test-rename-fstat.c
@@ -0,0 +1,23 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Test we can rename a file whilst holding an open fd which we fstat after renaming
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+int main()
+{
+    struct stat buf;
+    int err;
+    int fd = open("test-rename-fstat1", O_RDONLY);
+    err = rename("test-rename-fstat1", "test-rename-fstat1");
+    if (err)
+        return err;
+    return fstat(fd, &buf);
+}
diff --git a/test/test-rename-fstat.sh b/test/test-rename-fstat.sh
new file mode 100755
index 0000000..4ac89b8
--- /dev/null
+++ b/test/test-rename-fstat.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+
+rm -f test-rename-fstat1 test-rename-fstat2
+touch test-rename-fstat1
+# Will abort if it fails
+./test/test-rename-fstat
+ecode=$?
+rm -f test-rename-fstat1 test-rename-fstat2
+exit $ecode