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 #include +#include +#include + +#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 +#include 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 < -#include +#include +#include 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 ". + echo >&2 "Warning: Can't compile trivial program using ". 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 < +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 +#include +#include #include 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 : "", 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 +#include +#include +#include +#include +#include +#include + +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