/*
 * pseudo_client.c, pseudo client library code
 *
 * Copyright (c) 2008-2013 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: LGPL-2.1-only
 *
 */
#define _GNU_SOURCE

#include <stdio.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>

#ifdef PSEUDO_XATTRDB
#include <sys/xattr.h>
#endif

#include "pseudo.h"
#include "pseudo_ipc.h"
#include "pseudo_client.h"

/* GNU extension */
#if PSEUDO_PORT_LINUX
extern char *program_invocation_name;
#else
static char *program_invocation_name = "unknown";
#endif

static char *base_path(int dirfd, const char *path, int leave_last);

static int connect_fd = -1;
static int server_pid = 0;
int pseudo_prefix_dir_fd = -1;
int pseudo_localstate_dir_fd = -1;
int pseudo_pwd_fd = -1;
int pseudo_pwd_lck_fd = -1;
char *pseudo_pwd_lck_name = NULL;
FILE *pseudo_pwd = NULL;
int pseudo_grp_fd = -1;
FILE *pseudo_grp = NULL;
char *pseudo_cwd = NULL;
size_t pseudo_cwd_len;
char *pseudo_chroot = NULL;
char *pseudo_passwd = NULL;
size_t pseudo_chroot_len = 0;
char *pseudo_cwd_rel = NULL;
/* used for PSEUDO_DISABLED */
int pseudo_disabled = 0;
int pseudo_allow_fsync = 0;
static int pseudo_local_only = 0;
static int pseudo_client_logging = 1;

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
int pseudo_profile_fd = -1;
static int profile_interval = 1;
static pseudo_profile_t profile_data;
struct timeval *pseudo_wrapper_time = &profile_data.wrapper_time;
#endif
static int pseudo_inited = 0;

static int sent_messages = 0;

int pseudo_nosymlinkexp = 0;

/* note: these are int, not uid_t/gid_t, so I can use 'em with scanf */
uid_t pseudo_ruid;
uid_t pseudo_euid;
uid_t pseudo_suid;
uid_t pseudo_fuid;
gid_t pseudo_rgid;
gid_t pseudo_egid;
gid_t pseudo_sgid;
gid_t pseudo_fgid;

int (*pseudo_real_fork)(void) = fork;
int (*pseudo_real_execv)(const char *, char * const *) = execv;

#define PSEUDO_ETC_FILE(filename, realname, flags) pseudo_etc_file(filename, realname, flags, passwd_paths, npasswd_paths)

/* helper function to make a directory, just like mkdir -p.
 * Can't use system() because the child shell would end up trying
 * to do the same thing...
 */
static void
mkdir_p(char *path) {
	size_t len = strlen(path);
	size_t i;

	for (i = 1; i < len; ++i) {
		/* try to create all the directories in path, ignoring
		 * failures
		 */
		if (path[i] == '/') {
			path[i] = '\0';
			(void) mkdir(path, 0755);
			path[i] = '/';
		}
	}
	(void) mkdir(path, 0755);
}

/* Populating an array of unknown size is one of my least favorite
 * things. The idea here is to ensure that the logic flow is the same
 * both when counting expected items, and when populating them.
 */
static void
build_passwd_paths(void)
{
	int np = 0;
	int pass = 0;
	
	/* should never happen... */
	if (passwd_paths) {
		free(passwd_paths);
		passwd_paths = 0;
		npasswd_paths = 0;
	}

#define SHOW_PATH pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "passwd_paths[%d]: '%s'\n", np, (passwd_paths[np]))
#define ADD_PATH(p) do { if (passwd_paths) { passwd_paths[np] = (p); SHOW_PATH; } ++np; } while(0)
#define NUL_BYTE(p) do { if (passwd_paths) { *(p)++ = '\0'; } else { ++(p); } } while(0)

	do {
		if (pseudo_chroot) {
			ADD_PATH(pseudo_chroot);
		}
		if (pseudo_passwd) {
			char *s = pseudo_passwd;
			while (s) {
				char *t = strchr(s, ':');
				if (t) {
					NUL_BYTE(t);
				}
				ADD_PATH(s);
				s = t;
			}
		}
		if (PSEUDO_PASSWD_FALLBACK) {
			ADD_PATH(PSEUDO_PASSWD_FALLBACK);
		}

		/* allocation and/or return */
		if (passwd_paths) {
			if (np != npasswd_paths) {
				pseudo_diag("internal error: path allocation was inconsistent.\n");
			} else {
				/* yes, we allocated one extra for a trailing
				 * null pointer.
				 */
				passwd_paths[np] = NULL;
			}
			return;
		} else {
			passwd_paths = malloc((np + 1) * sizeof(*passwd_paths));
			npasswd_paths = np;
			if (!passwd_paths) {
				pseudo_diag("couldn't allocate storage for password paths.\n");
				exit(1);
			}
			np = 0;
		}
	} while (++pass < 2);
	/* in theory the second pass already returned, but. */
	pseudo_diag("should totally not have gotten here.\n");

	return;
}

#ifdef PSEUDO_XATTRDB
/* We really want to avoid calling the wrappers for these inside the
 * implementation. pseudo_wrappers will reinitialize these after it's
 * gotten the real_* found.
 */
ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t) = lgetxattr;
ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t) = fgetxattr;
int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int) = lsetxattr;
int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int) = fsetxattr;
/* Executive summary: We use an extended attribute,
 * user.pseudo_data, to store exactly the data we would otherwise
 * have stored in the database. Which is to say, uid, gid, mode, rdev.
 *
 * If we don't find a value, save an empty one with a lower version
 * number to indicate that we don't have data to reduce round trips.
 */
typedef struct {
	int version;
	uid_t uid;
	gid_t gid;
	mode_t mode;
	dev_t rdev;
} pseudo_db_data_t;

static pseudo_msg_t xattrdb_data;

pseudo_msg_t *
pseudo_xattrdb_save(int fd, const char *path, const struct stat64 *buf) {
	int rc = -1;
	if (!path && fd < 0)
		return NULL;
	if (!buf)
		return NULL;
	pseudo_db_data_t pseudo_db_data = {
		.version = 1,
		.uid = buf->st_uid,
		.gid = buf->st_gid,
		.mode = buf->st_mode,
		.rdev = buf->st_rdev
	};
	if (path) {
		rc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
	} else if (fd >= 0) {
		rc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
	}
	pseudo_debug(PDBGF_XATTRDB, "tried to save data for %s/%d: uid %d, mode %o, rc %d.\n",
		path ? path : "<nil>", fd, (int) pseudo_db_data.uid, (int) pseudo_db_data.mode, rc);
	/* none of the other fields are checked on save, and the value
	 * is currently only really used by mknod.
	 */
	if (rc == 0) {
		xattrdb_data.result = RESULT_SUCCEED;
		return &xattrdb_data;
	}
	return NULL;
}

pseudo_msg_t *
pseudo_xattrdb_load(int fd, const char *path, const struct stat64 *buf) {
	int rc = -1, retryrc = -1;
	if (!path && fd < 0)
		return NULL;
	/* don't try to getxattr on a thing unless we think it is
	 * likely to work.
	 */
	if (buf) {
		if (!S_ISDIR(buf->st_mode) && !S_ISREG(buf->st_mode)) {
			return NULL;
		}
	}
	pseudo_db_data_t pseudo_db_data;
	if (path) {
		rc = pseudo_real_lgetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data));
		if (rc == -1) {
			pseudo_db_data = (pseudo_db_data_t) { .version = 0 };
			retryrc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
		}
	} else if (fd >= 0) {
		rc = pseudo_real_fgetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data));
		if (rc == -1) {
			pseudo_db_data = (pseudo_db_data_t) { .version = 0 };
			retryrc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
		}
	}
	pseudo_debug(PDBGF_XATTRDB, "tried to load data for %s[%d]: rc %d, version %d.\n",
		path ? path : "<nil>", fd, rc, pseudo_db_data.version);
	if (rc == -1 && retryrc == 0) {
		/* there's no data, but there could have been; treat
		 * this as an empty database result.
		 */
		pseudo_debug(PDBGF_XATTRDB, "wrote version 0 for %s[%d]\n",
			path ? path : "<nil>", fd);
		xattrdb_data.result = RESULT_FAIL;
		return &xattrdb_data;
	} else if (rc == -1) {
		/* we can't create an extended attribute, so we may have
		 * used the database.
		 */
		return NULL;
	}
	/* Version 0 = just recording that we looked and found
	 * nothing.
	 * Version 1 = actually implemented.
	 */
	switch (pseudo_db_data.version) {
	case 0:
	default:
		xattrdb_data.result = RESULT_FAIL;
		break;
	case 1:
		xattrdb_data.uid = pseudo_db_data.uid;
		xattrdb_data.gid = pseudo_db_data.gid;
		xattrdb_data.mode = pseudo_db_data.mode;
		xattrdb_data.rdev = pseudo_db_data.rdev;
		xattrdb_data.result = RESULT_SUCCEED;
		break;
	}
	return &xattrdb_data;
}
#endif

#ifdef PSEUDO_PROFILING
static int pseudo_profile_pid = -2;

static void
pseudo_profile_start(void) {
	/* We use -1 as a starting value, and -2 as a value
	 * indicating not to try to open it.
	 */
	int existing_data = 0;
	int pid = getpid();
	if (pseudo_profile_pid > 0 && pseudo_profile_pid != pid) {
		/* looks like there's been a fork. We intentionally
		 * abandon any existing stats, since the parent might
		 * want to write them, and we want to combine with
		 * any previous stats for this pid.
		 */
		close(pseudo_profile_fd);
		pseudo_profile_fd = -1;
	}
	if (pseudo_profile_fd == -1) {
		int fd = -2;
		char *profile_path = pseudo_get_value("PSEUDO_PROFILE_PATH");
		pseudo_profile_pid = pid;
		if (profile_path) {
			fd = open(profile_path, O_RDWR | O_CREAT, 0600);
			if (fd >= 0) {
				fd = pseudo_fd(fd, MOVE_FD);
			}
			if (fd < 0) {
				fd = -2;
			} else {
				if (pid > 0) {
					pseudo_profile_t data;
					off_t rc;

					rc = lseek(fd, pid * sizeof(data), SEEK_SET);
					if (rc == (off_t) (pid * sizeof(data))) {
						rc = read(fd, &profile_data, sizeof(profile_data));
						/* cumulative with other values in same file */
						if (rc == sizeof(profile_data)) {
							pseudo_debug(PDBGF_PROFILE, "pid %d found existing profiling data.\n", pid);
							existing_data = 1;
							++profile_data.processes;
						} else {
							pseudo_debug(PDBGF_PROFILE, "read failed for pid %d: %d, %d\n", pid, (int) rc, errno);
						}
						profile_interval = 1;
					}
				}
			}
		}
		pseudo_profile_fd = fd;
	} else {
		pseudo_debug(PDBGF_PROFILE, "_start called with existing fd? (pid %d)", (int) pseudo_profile_pid);
		existing_data = 1;
		++profile_data.processes;
		profile_data.total_ops = 0;
		profile_data.messages = 0;
		profile_data.wrapper_time = (struct timeval) { .tv_sec = 0 };
		profile_data.op_time = (struct timeval) { .tv_sec = 0 };
		profile_data.ipc_time = (struct timeval) { .tv_sec = 0 };
	}
	if (!existing_data) {
		pseudo_debug(PDBGF_PROFILE, "pid %d found no existing profiling data.\n", pid);
		profile_data = (pseudo_profile_t) {
			.processes = 1,
			.total_ops = 0,
			.messages = 0,
			.wrapper_time = (struct timeval) { .tv_sec = 0 },
			.op_time = (struct timeval) { .tv_sec = 0 },
			.ipc_time = (struct timeval) { .tv_sec = 0 },
		};
	}
}

static inline void
fix_tv(struct timeval *tv) {
	if (tv->tv_usec > 1000000) {
		tv->tv_sec += tv->tv_usec / 1000000;
		tv->tv_usec %= 1000000;
	} else if (tv->tv_usec < 0) {
		/* C99 and later guarantee truncate-towards-zero.
		 * We want -1 through -1000000 usec to produce
		 * -1 seconds, etcetera. Note that sec is
		 * negative, so yes, we want to add to tv_sec and
		 * subtract from tv_usec.
		 */
		int sec = (tv->tv_usec - 999999) / 1000000;
		tv->tv_sec += sec;
		tv->tv_usec -= 1000000 * sec;
	}
}

static int profile_reported = 0;
static void
pseudo_profile_report(void) {
	if (pseudo_profile_fd < 0) {
		return;
	}
	fix_tv(&profile_data.wrapper_time);
	fix_tv(&profile_data.op_time);
	fix_tv(&profile_data.ipc_time);
	if (pseudo_profile_pid >= 0) {
		int rc1, rc2;
		rc1 = lseek(pseudo_profile_fd, pseudo_profile_pid * sizeof(profile_data), SEEK_SET);
		rc2 = write(pseudo_profile_fd, &profile_data, sizeof(profile_data));
		if (!profile_reported) {
			pseudo_debug(PDBGF_PROFILE, "pid %d (%s) saving profiling info: %d, %d.\n",
				pseudo_profile_pid,
				program_invocation_name ? program_invocation_name : "unknown",
				rc1, rc2);
			profile_reported = 1;
		}
	}
}
#endif

void
pseudo_init_client(void) {
	char *env;
	int need_free = 0;

	pseudo_antimagic();
	pseudo_new_pid();
	if (connect_fd != -1) {
		close(connect_fd);
		connect_fd = -1;
	}
#ifdef PSEUDO_PROFILING
	if (pseudo_profile_fd > -1) {
		close(pseudo_profile_fd);
	}
	pseudo_profile_fd = -1;
#endif

	/* in child processes, PSEUDO_DISABLED may have become set to
	 * some truthy value, in which case we'd disable pseudo,
	 * 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;
		switch (*env) {
		case '0':
		case 'f':
		case 'F':
		case 'n':
		case 'N':
			actually_disabled = 0;
			break;
		case 's':
		case 'S':
			actually_disabled = 0;
			pseudo_local_only = 1;
			break;
		}
		if (actually_disabled) {
			if (!pseudo_disabled) {
				pseudo_antimagic();
				pseudo_disabled = 1;
			}
			pseudo_set_value("PSEUDO_DISABLED", "1");
		} else {
			if (pseudo_disabled) {
				pseudo_magic();
				pseudo_disabled = 0;
				pseudo_inited = 0; /* Re-read the initial values! */
			}
			pseudo_set_value("PSEUDO_DISABLED", "0");
		}
	} 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);
	}
	if (env) {
		pseudo_allow_fsync = 1;
	} 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
	 * remove pseudo from the LD_PRELOAD. We need to make sure
	 * this value gets loaded into the internal variables.
	 *
	 * If we've been told to unload, but are still available
	 * we need to act as if unconditionally disabled.
	 */
	env = getenv("PSEUDO_UNLOAD");
	if (env) {
		pseudo_set_value("PSEUDO_UNLOAD", env);
		pseudo_disabled = 1;
	}

	/* Setup global items needed for pseudo to function... */
	if (!pseudo_inited) {
		/* Ensure that all of the values are reset */
		server_pid = 0;
		pseudo_prefix_dir_fd = -1;
		pseudo_localstate_dir_fd = -1;
		pseudo_pwd_fd = -1;
		pseudo_pwd_lck_fd = -1;
		pseudo_pwd_lck_name = NULL;
		pseudo_pwd = NULL;
		pseudo_grp_fd = -1;
		pseudo_grp = NULL;
		pseudo_cwd = NULL;
		pseudo_cwd_len = 0;
		pseudo_chroot = NULL;
		pseudo_passwd = NULL;
		pseudo_chroot_len = 0;
		pseudo_cwd_rel = NULL;
		pseudo_nosymlinkexp = 0;
	}

	if (!pseudo_disabled && !pseudo_inited) {
		char *pseudo_path = 0;

		pseudo_umask = umask(022);
		umask(pseudo_umask);

		pseudo_path = pseudo_prefix_path(NULL);
		if (pseudo_prefix_dir_fd == -1) {
			if (pseudo_path) {
				pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
				/* directory is missing? */
				if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) {
					pseudo_debug(PDBGF_CLIENT, "prefix directory '%s' doesn't exist, trying to create\n", pseudo_path);
					mkdir_p(pseudo_path);
					pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
				}
				pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, MOVE_FD);
			} else {
				pseudo_diag("No prefix available to to find server.\n");
				exit(1);
			}
			if (pseudo_prefix_dir_fd == -1) {
				pseudo_diag("Can't open prefix path '%s' for server: %s\n",
					pseudo_path,
					strerror(errno));
				exit(1);
			}
		}
		free(pseudo_path);
		pseudo_path = pseudo_localstatedir_path(NULL);
		if (pseudo_localstate_dir_fd == -1) {
			if (pseudo_path) {
				pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
				/* directory is missing? */
				if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) {
					pseudo_debug(PDBGF_CLIENT, "local state directory '%s' doesn't exist, trying to create\n", pseudo_path);
					mkdir_p(pseudo_path);
					pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
				}
				pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, MOVE_FD);
			} else {
				pseudo_diag("No local state directory available for server/file interactions.\n");
				exit(1);
			}
			if (pseudo_localstate_dir_fd == -1) {
				pseudo_diag("Can't open local state path '%s': %s\n",
					pseudo_path,
					strerror(errno));
				exit(1);
			}
		}
		free(pseudo_path);

		env = pseudo_get_value("PSEUDO_NOSYMLINKEXP");
		if (env) {
			char *endptr;
			/* if the environment variable is not an empty string,
			 * parse it; "0" means turn NOSYMLINKEXP off, "1" means
			 * turn it on (disabling the feature).  An empty string
			 * or something we can't parse means to set the flag; this
			 * is a safe default because if you didn't want the flag
			 * set, you normally wouldn't set the environment variable
			 * at all.
			 */
			if (*env) {
				pseudo_nosymlinkexp = strtol(env, &endptr, 10);
				if (*endptr)
					pseudo_nosymlinkexp = 1;
			} else {
				pseudo_nosymlinkexp = 1;
			}
		} else {
			pseudo_nosymlinkexp = 0;
		}
		free(env);
		env = pseudo_get_value("PSEUDO_UIDS");
		if (env)
			sscanf(env, "%d,%d,%d,%d",
				&pseudo_ruid, &pseudo_euid,
				&pseudo_suid, &pseudo_fuid);
		free(env);

		env = pseudo_get_value("PSEUDO_GIDS");
		if (env)
			sscanf(env, "%d,%d,%d,%d",
				&pseudo_rgid, &pseudo_egid,
				&pseudo_sgid, &pseudo_fuid);
		free(env);

		env = pseudo_get_value("PSEUDO_CHROOT");
		if (env) {
			pseudo_chroot = strdup(env);
			if (pseudo_chroot) {
				pseudo_chroot_len = strlen(pseudo_chroot);
			} else {
				pseudo_diag("Can't store chroot path '%s'\n", env);
			}
		}
		free(env);

		env = pseudo_get_value("PSEUDO_PASSWD");
		if (env) {
			/* note: this means that pseudo_passwd is a
			 * string we're allowed to modify... */
			pseudo_passwd = strdup(env);
		}
		free(env);
		build_passwd_paths();

		pseudo_inited = 1;
	}
	if (!pseudo_disabled) {
		pseudo_client_getcwd();
#ifdef PSEUDO_PROFILING
		pseudo_profile_start();
#endif
	}

	pseudo_magic();
}

static void
pseudo_file_close(int *fd, FILE **fp) {
	if (!fp || !fd) {
		pseudo_diag("pseudo_file_close: needs valid pointers.\n");
		return;
	}
	pseudo_antimagic();
	if (*fp) {
#if PSEUDO_PORT_DARWIN
		if (*fp == pseudo_host_etc_passwd_file) {
			endpwent();
		} else if (*fp != pseudo_host_etc_group_file) {
			endgrent();
		} else {
			fclose(*fp);
		}
#else
		fclose(*fp);
#endif
		*fd = -1;
		*fp = 0;
	}
#if PSEUDO_PORT_DARWIN
	if (*fd == pseudo_host_etc_passwd_fd ||
	    *fd == pseudo_host_etc_group_fd) {
		*fd = -1;
	}
#endif
	/* this should be impossible */
	if (*fd >= 0) {
		close(*fd);
		*fd = -1;
	}
	pseudo_magic();
}

static FILE *
pseudo_file_open(char *name, int *fd, FILE **fp) {
	if (!fp || !fd || !name) {
		pseudo_diag("pseudo_file_open: needs valid pointers.\n");
		return NULL;
	}
	pseudo_file_close(fd, fp);
	pseudo_antimagic();
	*fd = PSEUDO_ETC_FILE(name, NULL, O_RDONLY);
#if PSEUDO_PORT_DARWIN
	if (*fd == pseudo_host_etc_passwd_fd) {
		*fp = pseudo_host_etc_passwd_file;
		setpwent();
	} else if (*fd == pseudo_host_etc_group_fd) {
		*fp = pseudo_host_etc_group_file;
		setgrent();
	}
#endif
	if (*fd >= 0) {
		*fd = pseudo_fd(*fd, MOVE_FD);
		*fp = fdopen(*fd, "r");
		if (!*fp) {
			close(*fd);
			*fd = -1;
		}
	}
	pseudo_magic();
	return *fp;
}

/* there is no spec I know of requiring us to defend this fd
 * against being closed by the user.
 */
int
pseudo_pwd_lck_open(void) {
	pseudo_pwd_lck_close();
	if (!pseudo_pwd_lck_name) {
		pseudo_pwd_lck_name = malloc(pseudo_path_max());
		if (!pseudo_pwd_lck_name) {
			pseudo_diag("couldn't allocate space for passwd lockfile path.\n");
			return -1;
		}
	}
	pseudo_antimagic();
	pseudo_pwd_lck_fd = PSEUDO_ETC_FILE(".pwd.lock",
					pseudo_pwd_lck_name, O_RDWR | O_CREAT);
	pseudo_magic();
	return pseudo_pwd_lck_fd;
}

int
pseudo_pwd_lck_close(void) {
	if (pseudo_pwd_lck_fd != -1) {
		pseudo_antimagic();
		close(pseudo_pwd_lck_fd);
		if (pseudo_pwd_lck_name) {
			unlink(pseudo_pwd_lck_name);
			free(pseudo_pwd_lck_name);
			pseudo_pwd_lck_name = 0;
		}
		pseudo_magic();
		pseudo_pwd_lck_fd = -1;
		return 0;
	} else {
		return -1;
	}
}

FILE *
pseudo_pwd_open(void) {
	return pseudo_file_open("passwd", &pseudo_pwd_fd, &pseudo_pwd);
}

void
pseudo_pwd_close(void) {
	pseudo_file_close(&pseudo_pwd_fd, &pseudo_pwd);
}

FILE *
pseudo_grp_open(void) {
	return pseudo_file_open("group", &pseudo_grp_fd, &pseudo_grp);
}

void
pseudo_grp_close(void) {
	pseudo_file_close(&pseudo_grp_fd, &pseudo_grp);
}

void
pseudo_client_touchuid(void) {
	static char uidbuf[256];
	snprintf(uidbuf, 256, "%d,%d,%d,%d",
		pseudo_ruid, pseudo_euid, pseudo_suid, pseudo_fuid);
	pseudo_set_value("PSEUDO_UIDS", uidbuf);
}

void
pseudo_client_touchgid(void) {
	static char gidbuf[256];
	snprintf(gidbuf, 256, "%d,%d,%d,%d",
		pseudo_rgid, pseudo_egid, pseudo_sgid, pseudo_fgid);
	pseudo_set_value("PSEUDO_GIDS", gidbuf);
}

int
pseudo_client_chroot(const char *path) {
	/* free old value */
	free(pseudo_chroot);

	pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "client chroot: %s\n", path);
	if (!strcmp(path, "/")) {
		pseudo_chroot_len = 0;
		pseudo_chroot = 0;
		pseudo_set_value("PSEUDO_CHROOT", NULL);
		return 0;
	}
	/* allocate new value */
	pseudo_chroot_len = strlen(path);
	pseudo_chroot = malloc(pseudo_chroot_len + 1);
	if (!pseudo_chroot) {
		pseudo_diag("Couldn't allocate chroot directory buffer.\n");
		pseudo_chroot_len = 0;
		errno = ENOMEM;
		return -1;
	}
	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;
}

char *
pseudo_root_path(const char *func, int line, int dirfd, const char *path, int leave_last) {
	char *rc;
	pseudo_antimagic();
	rc = base_path(dirfd, path, leave_last);
	pseudo_magic();
	if (!rc) {
		pseudo_diag("couldn't allocate absolute path for '%s'.\n",
			path ? path : "null");
	}
	pseudo_debug(PDBGF_CHROOT, "root_path [%s, %d]: '%s' from '%s'\n",
		func, line,
		rc ? rc : "<nil>",
		path ? path : "<nil>");
	return rc;
}

int
pseudo_client_getcwd(void) {
	char *cwd;
	cwd = malloc(pseudo_path_max());
	if (!cwd) {
		pseudo_diag("Can't allocate CWD buffer!\n");
		return -1;
	}
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd: trying to find cwd.\n");
	if (getcwd(cwd, pseudo_path_max())) {
		/* cwd now holds a canonical path to current directory */
		free(pseudo_cwd);
		pseudo_cwd = cwd;
		pseudo_cwd_len = strlen(pseudo_cwd);
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd okay: [%s] %d bytes\n", pseudo_cwd, (int) pseudo_cwd_len);
		if (pseudo_chroot_len &&
			pseudo_cwd_len >= pseudo_chroot_len &&
			!memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len) &&
			(pseudo_cwd[pseudo_chroot_len] == '\0' ||
			pseudo_cwd[pseudo_chroot_len] == '/')) {
			pseudo_cwd_rel = pseudo_cwd + pseudo_chroot_len;
		} else {
			pseudo_cwd_rel = pseudo_cwd;
		}
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "abscwd: <%s>\n", pseudo_cwd);
		if (pseudo_cwd_rel != pseudo_cwd) {
			pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "relcwd: <%s>\n", pseudo_cwd_rel);
		}
		return 0;
	} else {
		pseudo_diag("Can't get CWD: %s\n", strerror(errno));
		return -1;
	}
}

static char *
fd_path(int fd) {
	if (fd >= 0 && fd < nfds) {
		return fd_paths[fd];
	}
	if (fd == AT_FDCWD) {
		return pseudo_cwd;
	}
	return 0;
}

static void
pseudo_client_path_set(int fd, const char *path, char ***patharray, int *len) {
	if (fd < 0)
		return;

	if (fd >= *len) {
		int i;
		pseudo_debug(PDBGF_CLIENT, "expanding fds from %d to %d\n",
			*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 ((*patharray)[fd]) {
			pseudo_debug(PDBGF_CLIENT, "reopening fd %d [%s] -- didn't see close\n",
				fd, (*patharray)[fd]);
		}
		/* yes, it is safe to free null pointers. yay for C89 */
		free((*patharray)[fd]);
		(*patharray)[fd] = 0;
	}
	if (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)
		return;

	free(fd_paths[fd]);
	fd_paths[fd] = 0;

	if (fd < linked_nfds) {
		free(linked_fd_paths[fd]);
		linked_fd_paths[fd] = 0;
	}
}

static void
pseudo_client_closefrom(int fd) {
	int i;
	if (fd < 0 || fd >= nfds)
		return;

	for (i = fd; i < nfds; ++i) {
		free(fd_paths[i]);
		fd_paths[i] = 0;

		if (i < linked_nfds) {
			free(linked_fd_paths[i]);
			linked_fd_paths[i] = 0;
		}
	}
}

/* spawn server */
static int
client_spawn_server(void) {
	int status;
	FILE *fp;
	char * pseudo_pidfile;

	if ((server_pid = pseudo_real_fork()) != 0) {
		if (server_pid == -1) {
			pseudo_diag("couldn't fork server: %s\n", strerror(errno));
			return 1;
		}
		pseudo_evlog(PDBGF_CLIENT, "spawned new server, pid %d\n", server_pid);
		pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "spawned server, pid %d\n", server_pid);
		/* wait for the child process to terminate, indicating server
		 * is ready
		 */
		waitpid(server_pid, &status, 0);
		if (WIFEXITED(status)) {
			pseudo_evlog(PDBGF_CLIENT, "server exited status %d\n", WEXITSTATUS(status));
		}
		if (WIFSIGNALED(status)) {
			pseudo_evlog(PDBGF_CLIENT, "server exited from signal %d\n", WTERMSIG(status));
		}
		server_pid = -2;
		if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
			pseudo_evlog(PDBGF_CLIENT, "server reports successful startup, reading pid.\n");
			pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE);
			fp = fopen(pseudo_pidfile, "r");
			if (fp) {
				if (fscanf(fp, "%d", &server_pid) != 1) {
					pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n");
				}
				fclose(fp);
			} else {
				pseudo_debug(PDBGF_CLIENT, "no pid file (%s): %s\n",
					pseudo_pidfile, strerror(errno));
			}
			pseudo_debug(PDBGF_CLIENT, "read new pid file: %d\n", server_pid);
			free(pseudo_pidfile);
			/* at this point, we should have a new server_pid */
			return 0;
		} else {
			pseudo_evlog(PDBGF_CLIENT, "server startup apparently unsuccessful.  setting server pid to -1.\n");
			server_pid = -1;
			return 1;
		}
	} else {
		char *base_args[] = { NULL, NULL, NULL };
		char **argv;
		char *option_string = pseudo_get_value("PSEUDO_OPTS");
		int args;
		int fd;

		pseudo_new_pid();
		base_args[0] = pseudo_bindir_path("pseudo");
		base_args[1] = "-d";
		if (option_string) {
			char *s;
			int arg;

			/* count arguments in PSEUDO_OPTS, starting at 2
			 * for pseudo/-d/NULL, plus one for the option string.
			 * The number of additional arguments may be less
			 * than the number of spaces, but can't be more.
			 */
			args = 4;
			for (s = option_string; *s; ++s)
				if (*s == ' ')
					++args;

			argv = malloc(args * sizeof(char *));
			argv[0] = base_args[0];
			argv[1] = base_args[1];
			arg = 2;
			while ((s = strsep(&option_string, " ")) != NULL) {
				if (*s) {
					argv[arg++] = strdup(s);
				}
			}
			argv[arg] = 0;
		} else {
			argv = base_args;
		}

		/* close any higher-numbered fds which might be open,
		 * such as sockets.  We don't have to worry about 0 and 1;
		 * the server closes them already, and more importantly,
		 * they can't have been opened or closed without us already
		 * having spawned a server... The issue is just socket()
		 * calls which could result in fds being left open, and those
		 * can't overwrite fds 0-2 unless we closed them...
		 * 
		 * No, really.  It works.
		 */
		for (fd = 3; fd < 1024; ++fd) {
			if (fd != pseudo_util_debug_fd)
				close(fd);
		}
		/* and now, execute the server */
		pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER | PDBGF_INVOKE, "calling execv on %s\n", argv[0]);

		/* don't try to log this exec, because it'll cause the process
		 * that is supposed to be spawning the server to try to spawn
		 * a server. Whoops. This is because the exec wrapper doesn't
		 * respect antimagic, which I believe is intentional.
		 */
		pseudo_client_logging = 0;

		/* manual setup of environment, so we can call real-execv
		 * instead of the wrapper.
		 */
		pseudo_set_value("PSEUDO_UNLOAD", "YES");
		pseudo_setupenv();
		pseudo_dropenv();
		pseudo_real_execv(argv[0], argv);
		pseudo_diag("critical failure: exec of pseudo daemon failed: %s\n", strerror(errno));
		exit(1);
	}
}

static int
client_ping(void) {
	pseudo_msg_t ping;
	pseudo_msg_t *ack;
	char tagbuf[pseudo_path_max()];
	char *tag = pseudo_get_value("PSEUDO_TAG");

	memset(&ping, 0, sizeof(ping));

	ping.type = PSEUDO_MSG_PING;
	ping.op = OP_NONE;

	ping.pathlen = snprintf(tagbuf, sizeof(tagbuf), "%s%c%s",
		program_invocation_name ? program_invocation_name : "<unknown>",
		0,
		tag ? tag : "");
	free(tag);
	ping.client = getpid();
	ping.result = 0;
	errno = 0;
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending ping\n");
	if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tagbuf)) {
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "error pinging server: %s\n", strerror(errno));
		return 1;
	}
	ack = pseudo_msg_receive(connect_fd);
	if (!ack) {
		pseudo_debug(PDBGF_CLIENT, "no ping response from server: %s\n", strerror(errno));
		/* and that's not good, so... */
		server_pid = 0;
		return 1;
	}
	if (ack->type != PSEUDO_MSG_ACK) {
		pseudo_debug(PDBGF_CLIENT, "invalid ping response from server: expected ack, got %d\n", ack->type);
		/* and that's not good, so... */
		server_pid = 0;
		return 1;
	} else {
                /* The server tells us whether or not to log things. */
                if (ack->result == RESULT_SUCCEED) {
                        pseudo_client_logging = 1;
                } else {
                        pseudo_client_logging = 0;
                }
        }
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "ping ok\n");
	return 0;
}

static void
void_client_ping(void) {
	client_ping();
}

int
pseudo_fd(int fd, int how) {
	int newfd;

	if (fd < 0)
		return(-1);

	/* If already above PSEUDO_MIN_FD, no need to move */
	if ((how == MOVE_FD) && (fd >= PSEUDO_MIN_FD)) {
		newfd = fd;
	} else {
		newfd = fcntl(fd, F_DUPFD, PSEUDO_MIN_FD);

		if (how == MOVE_FD)
			close(fd);
	}

	/* Set close on exec, even if we didn't move it. */
	if ((newfd >= 0) && (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0))
		pseudo_diag("Can't set close on exec flag: %s\n",
			strerror(errno));

	return(newfd);
}

static int
client_connect(void) {
	/* we have a server pid, is it responsive? */
	struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET };
	int cwd_fd;

#if PSEUDO_PORT_DARWIN
	sun.sun_len = strlen(PSEUDO_SOCKET) + 1;
#endif

	connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
	connect_fd = pseudo_fd(connect_fd, MOVE_FD);
	pseudo_evlog(PDBGF_CLIENT, "creating socket %s.\n", sun.sun_path);
	if (connect_fd == -1) {
		char *e = strerror(errno);
		pseudo_diag("Can't create socket: %s (%s)\n", sun.sun_path, e);
		pseudo_evlog(PDBGF_CLIENT, "failed to create socket: %s\n", e);
		return 1;
	}

	pseudo_debug(PDBGF_CLIENT, "connecting socket...\n");
	cwd_fd = open(".", O_RDONLY);
	if (cwd_fd == -1) {
		pseudo_diag("Couldn't stash directory before opening socket: %s",
			strerror(errno));
		close(connect_fd);
		connect_fd = -1;
		return 1;
	}
	if (fchdir(pseudo_localstate_dir_fd) == -1) {
		pseudo_diag("Couldn't chdir to server directory [%d]: %s\n",
			pseudo_localstate_dir_fd, strerror(errno));
		close(connect_fd);
		close(cwd_fd);
		connect_fd = -1;
		return 1;
	}
	if (connect(connect_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) {
		char *e = strerror(errno);
		pseudo_debug(PDBGF_CLIENT, "Can't connect socket to pseudo.socket: (%s)\n", e);
		pseudo_evlog(PDBGF_CLIENT, "connect failed: %s\n", e);
		close(connect_fd);
		if (fchdir(cwd_fd) == -1) {
			pseudo_diag("return to previous directory failed: %s\n",
				strerror(errno));
		}
		close(cwd_fd);
		connect_fd = -1;
		return 1;
	}
	if (fchdir(cwd_fd) == -1) {
		pseudo_diag("return to previous directory failed: %s\n",
			strerror(errno));
	}
	close(cwd_fd);
	pseudo_evlog(PDBGF_CLIENT, "socket connect OK.\n");
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connected socket.\n");
	return 0;
}

static int
pseudo_client_setup(void) {
	char * pseudo_pidfile;
	FILE *fp;
	server_pid = 0;

	/* avoid descriptor leak, I hope */
	if (connect_fd >= 0) {
		close(connect_fd);
		connect_fd = -1;
	}
	if (client_connect() == 0) {
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connection started.\n");
		if (client_ping() == 0) {
			pseudo_debug(PDBGF_CLIENT, "connection ping okay, server ready.\n");
			return 0;
		} else {
			pseudo_debug(PDBGF_CLIENT, "connection up, but ping failed.\n");
			/* and now we'll close the connection, and try to
			 * kick the server.
			 */
			close(connect_fd);
			connect_fd = -1;
		}
	}
	pseudo_debug(PDBGF_CLIENT, "connection failed, checking server...\n");

	/* This whole section is purely informational; we want to know
	 * what might be up with the server, but we're going to try to
	 * start one anyway.
	 */
	pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE);
	fp = fopen(pseudo_pidfile, "r");
	free(pseudo_pidfile);
	if (fp) {
		if (fscanf(fp, "%d", &server_pid) != 1) {
			pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n");
		}
		fclose(fp);
	}
	if (server_pid > 0) {
		if (kill(server_pid, 0) == -1) {
			pseudo_debug(PDBGF_CLIENT, "couldn't find server at pid %d: %s\n",
				server_pid, strerror(errno));
			server_pid = 0;
		} else {
			pseudo_debug(PDBGF_CLIENT, "server pid should be %d, which exists, but connection failed.\n",
				server_pid);
			/* and we'll restart the server anyway. */
		}
	} else {
		pseudo_debug(PDBGF_CLIENT, "no server pid available.\n");
	}
	if (client_spawn_server()) {

		pseudo_evlog(PDBGF_CLIENT, "attempted respawn, failed.\n");
		pseudo_debug(PDBGF_CLIENT, "failed to spawn server, waiting for retry.\n");
		return 1;
	} else {
		pseudo_evlog(PDBGF_CLIENT, "restarted, new pid %d, will retry message\n", server_pid);
		pseudo_debug(PDBGF_CLIENT, "restarted, new pid %d, will retry message\n", server_pid);
		/* so we think a server has started. Now we'll retry the
		 * connect/ping, because if they work we'll have set
		 * connect_fd correctly, and will have succeeded.
		 */
		if (!client_connect()) {
			pseudo_debug(PDBGF_CLIENT, "connect okay to restarted server.\n");
			pseudo_evlog(PDBGF_CLIENT, "connect okay to restarted server.\n");
			if (!client_ping()) {
				pseudo_debug(PDBGF_CLIENT, "ping okay to restarted server.\n");
				pseudo_evlog(PDBGF_CLIENT, "ping okay to restarted server.\n");
				return 0;
			} else {
				pseudo_debug(PDBGF_CLIENT, "ping failed to restarted server.\n");
				pseudo_evlog(PDBGF_CLIENT, "ping failed to restarted server.\n");
				close(connect_fd);
				connect_fd = -1;
				return 1;
			}
		} else {
			pseudo_debug(PDBGF_CLIENT, "connect failed to restarted server.\n");
			pseudo_evlog(PDBGF_CLIENT, "connect failed to restarted server.\n");
			return 1;
		}
	}
}

#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;
	int tries = 0;
	int rc;
	extern char *program_invocation_short_name;
	#if 0
	if (!strcmp(program_invocation_short_name, "pseudo"))
		abort();
	#endif

	if (!msg)
		return 0;

	/* Try to send a message. If sending fails, try to spawn a server,
	 * and whether or not we succeed, wait a little bit and retry sending.
	 * It's okay if we can't start a server sometimes, because another
	 * client may have done it.
	 */
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending a message: ino %llu\n",
		(unsigned long long) msg->ino);
        for (tries = 0; tries < PSEUDO_RETRIES; ++tries) {
		pseudo_evlog(PDBGF_CLIENT, "try %d, connect fd is %d\n", tries, connect_fd);
		rc = pseudo_msg_send(connect_fd, msg, len, path);
		if (rc != 0) {
			pseudo_evlog(PDBGF_CLIENT, "msg_send failed [%d].\n", rc);
			pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "msg_send: %d%s\n",
				rc,
				rc == -1 ? " (sigpipe)" :
					   " (short write)");
			pseudo_debug(PDBGF_CLIENT, "trying to get server, try %d\n", tries);
			/* try to open server; if we fail, wait a bit before
			 * retry.
			 */
			if (pseudo_client_setup()) {
				int ms = (getpid() % 5) + (3 * tries);
				struct timespec delay = { .tv_sec = 0, .tv_nsec = ms * 1000000 };
				pseudo_evlog(PDBGF_CLIENT, "setup failed, delaying %d ms.\n", ms);
				nanosleep(&delay, NULL);
			} else {
				pseudo_evlog(PDBGF_CLIENT, "setup apparently successful, retrying message immediately.\n");
			}
			continue;
		}
		/* note "continue" above; we only get here if rc was 0,
		 * indicating a successful send.
		 */
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sent!\n");
		response = pseudo_msg_receive(connect_fd);
		if (!response) {
			pseudo_debug(PDBGF_CLIENT, "expected response did not occur; retrying\n");
		} else {
			if (response->type != PSEUDO_MSG_ACK) {
				pseudo_debug(PDBGF_CLIENT, "got non-ack response %d\n", response->type);
				return 0;
			} else {
				pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "got response type %d\n", response->type);
				return response;
			}
		}
	}
	pseudo_diag("pseudo: server connection persistently failed, aborting.\n");
	pseudo_evlog_dump();
	pseudo_diag("event log dumped, aborting.\n");
	abort();
	pseudo_diag("aborted.\n");
	return 0;
}

int
pseudo_client_shutdown(int wait_on_socket) {
	pseudo_msg_t msg;
	pseudo_msg_t *ack;
	char *pseudo_path;

	pseudo_debug(PDBGF_INVOKE, "attempting to shut down server.\n");
	pseudo_path = pseudo_prefix_path(NULL);
	if (pseudo_prefix_dir_fd == -1) {
		if (pseudo_path) {
			pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
			/* directory is missing? */
			if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) {
				pseudo_debug(PDBGF_CLIENT, "prefix directory doesn't exist, trying to create\n");
				mkdir_p(pseudo_path);
				pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
			}
			pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, COPY_FD);
			free(pseudo_path);
		} else {
			pseudo_diag("No prefix available to to find server.\n");
			exit(1);
		}
		if (pseudo_prefix_dir_fd == -1) {
			pseudo_diag("Can't open prefix path (%s) for server. (%s)\n",
				pseudo_prefix_path(NULL),
				strerror(errno));
			exit(1);
		}
	}
	pseudo_path = pseudo_localstatedir_path(NULL);
	mkdir_p(pseudo_path);
	if (pseudo_localstate_dir_fd == -1) {
		if (pseudo_path) {
			pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
			/* directory is missing? */
			if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) {
				pseudo_debug(PDBGF_CLIENT, "local state dir doesn't exist, trying to create\n");
				mkdir_p(pseudo_path);
				pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
			}
			pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, COPY_FD);
			free(pseudo_path);
		} else {
			pseudo_diag("No prefix available to to find server.\n");
			exit(1);
		}
		if (pseudo_localstate_dir_fd == -1) {
			pseudo_diag("Can't open local state path (%s) for server. (%s)\n",
				pseudo_localstatedir_path(NULL),
				strerror(errno));
			exit(1);
		}
	}
	if (client_connect()) {
		pseudo_debug(PDBGF_INVOKE, "Pseudo server seems to be already offline.\n");
		return 0;
	}
	memset(&msg, 0, sizeof(pseudo_msg_t));
	msg.type = PSEUDO_MSG_SHUTDOWN;
	msg.op = OP_NONE;
	msg.client = getpid();
	pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "sending shutdown request\n");
	if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) {
		pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "error requesting shutdown: %s\n", strerror(errno));
		return 1;
	}
	ack = pseudo_msg_receive(connect_fd);
	if (!ack) {
		pseudo_diag("server did not respond to shutdown query.\n");
		return 1;
	}
	if (ack->type != PSEUDO_MSG_ACK) {
		pseudo_diag("Server refused shutdown.  Remaining client fds: %d\n", ack->fd);
		pseudo_diag("Client pids: %s\n", ack->path);
		pseudo_diag("Server will shut down after all clients exit.\n");
	}
	if (wait_on_socket) {
		/* try to receive a message the server won't send;
		 * this should abort/error-out when the server actually
		 * shuts down. */
		ack = pseudo_msg_receive(connect_fd);
	}
	return 0;
}

static char *
base_path(int dirfd, const char *path, int leave_last) {
	char *basepath = 0;
	size_t baselen = 0;
	size_t minlen = 0;
	char *newpath;

	if (!path)
		return NULL;

	if (!*path) {
		if (dirfd != -1 && dirfd != AT_FDCWD)
			return fd_path(dirfd);
		return "";
	}

	if (path[0] != '/') {
		if (dirfd != -1 && dirfd != AT_FDCWD) {
			if (dirfd >= 0) {
				basepath = fd_path(dirfd);
			}
			if (basepath) {
				baselen = strlen(basepath);
			} else {
				pseudo_diag("got *at() syscall for unknown directory, fd %d\n", dirfd);
			}
		} else {
			basepath = pseudo_cwd;
			baselen = pseudo_cwd_len;
		}
		if (!basepath) {
			pseudo_diag("unknown base path for fd %d, path %s\n", dirfd, path);
			return 0;
		}
		/* if there's a chroot path, and it's the start of basepath,
		 * flag it for pseudo_fix_path
		 */
		if (pseudo_chroot_len && baselen >= pseudo_chroot_len &&
			!memcmp(basepath, pseudo_chroot, pseudo_chroot_len) &&
			(basepath[pseudo_chroot_len] == '\0' || basepath[pseudo_chroot_len] == '/')) {

			minlen = pseudo_chroot_len;
		}
	} else if (pseudo_chroot_len) {
		/* "absolute" is really relative to chroot path */
		basepath = pseudo_chroot;
		baselen = pseudo_chroot_len;
		minlen = pseudo_chroot_len;
	}

	newpath = pseudo_fix_path(basepath, path, minlen, baselen, NULL, leave_last);
	pseudo_debug(PDBGF_PATH, "base_path[%s]: %s</>%s => %s\n",
		leave_last ? "nofollow" : "follow",
		basepath ? basepath : "<nil>",
		path ? path : "<nil>",
		newpath ? newpath : "<nil>");
	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;
	static pseudo_msg_t msg;
	msg = (pseudo_msg_t) { .type = PSEUDO_MSG_OP };
	size_t pathlen = -1;
	int do_request = 0;
	char *path_extra_1 = 0;
	size_t path_extra_1len = 0;
	char *path_extra_2 = 0;
	size_t path_extra_2len = 0;
	static char *alloced_path = 0;
	static size_t alloced_len = 0;
	int strip_slash;
	int startfd, i;

#ifdef PSEUDO_PROFILING
	struct timeval tv1_op, tv2_op;

	gettimeofday(&tv1_op, NULL);
	++profile_data.total_ops;
#endif
	/* disable wrappers */
	pseudo_antimagic();

	/* note: I am not entirely sure this should happen even if no
	 * messages have actually been sent. */
	if (!sent_messages) {
		sent_messages = 1;
		atexit(void_client_ping);
	}

	/* if path isn't available, try to find one? */
	if (!path && fd >= 0 && fd <= nfds) {
		path = fd_path(fd);
		if (!path) {
			pathlen = 0;
		} else {
			pathlen = strlen(path) + 1;
		}
	}

	if (op != OP_CHROOT && op != OP_CHDIR && op != OP_CLOSE && op != OP_CLOSEFROM && op != OP_DUP
			&& pseudo_client_ignore_path_chroot(path, 0)) {
		if (op == OP_OPEN) {
			/* Sanitise the path to have no trailing slash as this is convention in the database */
			char *stripped_path;
			pathlen = strlen(path);
			if (pathlen > 2 && (path[pathlen - 1]) == '/') {
				stripped_path = strndup(path, pathlen-1);
				pseudo_client_path(fd, stripped_path);
				free(stripped_path);
			} else {
				pseudo_client_path(fd, path);
			}
		}
		/* reenable wrappers */
		pseudo_magic();
		return result;
	}

#ifdef PSEUDO_XATTRDB
	if (buf) {
		struct stat64 bufcopy = *buf;
		int do_save = 0;
		/* maybe use xattr instead */
		/* note: if we use xattr, logging won't work reliably
		 * because the server won't get messages if these work.
		 */
		switch (op) {
		case OP_CHMOD:
		case OP_FCHMOD:
		case OP_CHOWN:
		case OP_FCHOWN:
			/* for these, we want to start with the existing db
			 * values.
			 */
			bufcopy = *buf;
			result = pseudo_xattrdb_load(fd, path, buf);
			if (result && result->result == RESULT_SUCCEED) {
				pseudo_debug(PDBGF_XATTR, "merging existing values for xattr\n");
				switch (op) {
				case OP_CHMOD:
				case OP_FCHMOD:
					bufcopy.st_uid = result->uid;
					bufcopy.st_gid = result->gid;
					break;
				case OP_CHOWN:
				case OP_FCHOWN:
					bufcopy.st_rdev = result->rdev;
					bufcopy.st_mode = result->mode;
					break;
				default:
					break;
				}
				
			} else {
				switch (op) {
				case OP_CHMOD:
				case OP_FCHMOD:
					bufcopy.st_uid = pseudo_fuid;
					bufcopy.st_gid = pseudo_fgid;
					break;
				default:
					break;
				}
			}
			result = NULL;
			do_save = 1;
			break;
		case OP_CREAT:
		case OP_MKDIR:
		case OP_MKNOD:
			bufcopy.st_uid = pseudo_fuid;
			bufcopy.st_gid = pseudo_fgid;
			do_save = 1;
			break;
		case OP_LINK:
			do_save = 1;
			break;
		case OP_FSTAT:
		case OP_STAT:
			result = pseudo_xattrdb_load(fd, path, buf);
			break;
		default:
			break;
		}
		if (do_save) {
			result = pseudo_xattrdb_save(fd, path, &bufcopy);
		}
		if (result)
			goto skip_path;
	}
#endif

	if (op == OP_RENAME) {
		va_list ap;
		if (!path) {
			pseudo_diag("rename (%s) without new path.\n",
				path ? path : "<nil>");
			pseudo_magic();
			return 0;
		}
		va_start(ap, buf);
		path_extra_1 = va_arg(ap, char *);
		va_end(ap);
		/* last argument is the previous path of the file */
		if (!path_extra_1) {
			pseudo_diag("rename (%s) without old path.\n",
				path ? path : "<nil>");
			pseudo_magic();
			return 0;
		}
		path_extra_1len = strlen(path_extra_1);
		pseudo_debug(PDBGF_PATH | PDBGF_FILE, "rename: %s -> %s\n",
			path_extra_1, path);
	}

	/* we treat the "create" and "replace" flags as logically
	 * distinct operations, because they can fail when set can't.
	 */
	if (op == OP_SET_XATTR || op == OP_CREATE_XATTR || op == OP_REPLACE_XATTR) {
		va_list ap;
		va_start(ap, buf);
		path_extra_1 = va_arg(ap, char *);
		path_extra_1len = strlen(path_extra_1);
		path_extra_2 = va_arg(ap, char *);
		path_extra_2len = va_arg(ap, size_t);
		va_end(ap);
		pseudo_debug(PDBGF_XATTR, "setxattr, name '%s', value %d bytes\n",
			path_extra_1, (int) path_extra_2len);
		pseudo_debug_call(PDBGF_XATTR | PDBGF_VERBOSE, pseudo_dump_data, "xattr value", path_extra_2, path_extra_2len);
	}
	if (op == OP_GET_XATTR || op == OP_REMOVE_XATTR) {
		va_list ap;
		va_start(ap, buf);
		path_extra_1 = va_arg(ap, char *);
		path_extra_1len = strlen(path_extra_1);
		va_end(ap);
		pseudo_debug(PDBGF_XATTR, "%sxattr, name '%s'\n",
			op == OP_GET_XATTR ? "get" : "remove", path_extra_1);
	}

	if (path) {
		if (pathlen == (size_t) -1) {
			pathlen = strlen(path) + 1;
		}
		/* path fixup has to happen in the specific functions,
		 * because they may have to make calls which have to be
		 * fixed up for chroot stuff already.
		 * ... but wait!  / in chroot should not have that
		 * trailing /.
		 * (no attempt is made to handle a rename of "/" occurring
		 * in a chroot...)
		 */
		strip_slash = (pathlen > 2 && (path[pathlen - 2]) == '/');
	} else {
		path = "";
		pathlen = 0;
		strip_slash = 0;
	}

	/* f*xattr operations can result in needing to send a path
	 * value even though we don't have one available. We use an
	 * empty path for that.
	 */
	if (path_extra_1) {
		size_t full_len = path_extra_1len + 1 + pathlen - strip_slash;
		size_t partial_len = pathlen - 1 - strip_slash;
		if (path_extra_2) {
			full_len += path_extra_2len + 1;
		}
		if (full_len > alloced_len) {
			free(alloced_path);
			alloced_path = malloc(full_len);
			alloced_len = full_len;
			if (!alloced_path) {
				pseudo_diag("Can't allocate space for paths for a rename operation.  Sorry.\n");
				alloced_len = 0;
				pseudo_magic();
				return 0;
			}
		}
		memcpy(alloced_path, path, partial_len);
		alloced_path[partial_len] = '\0';
		memcpy(alloced_path + partial_len + 1, path_extra_1, path_extra_1len);
		alloced_path[partial_len + path_extra_1len + 1] = '\0';
		if (path_extra_2) {
			memcpy(alloced_path + partial_len + path_extra_1len + 2, path_extra_2, path_extra_2len);
		}
		alloced_path[full_len - 1] = '\0';
		path = alloced_path;
		pathlen = full_len;
		pseudo_debug_call(PDBGF_IPC | PDBGF_VERBOSE, pseudo_dump_data, "combined path buffer", path, pathlen);
	} else {
		if (strip_slash) {
			if (pathlen > alloced_len) {
				free(alloced_path);
				alloced_path = malloc(pathlen);
				alloced_len = pathlen;
				if (!alloced_path) {
					pseudo_diag("Can't allocate space for paths for a rename operation.  Sorry.\n");
					alloced_len = 0;
					pseudo_magic();
					return 0;
				}
			}
			memcpy(alloced_path, path, pathlen);
			alloced_path[pathlen - 2] = '\0';
			path = alloced_path;
		}
	}

#ifdef PSEUDO_XATTRDB
	/* If we were able to store things in xattr, we can easily skip
	 * most of the fancy path computations and such.
	 */
	skip_path:
#endif

	if (buf)
		pseudo_msg_stat(&msg, buf);

	if (pseudo_util_debug_flags & PDBGF_OP) {
		pseudo_debug(PDBGF_OP, "%s%s", pseudo_op_name(op),
			(dirfd != -1 && dirfd != AT_FDCWD && op != OP_DUP) ? "at" : "");
		if (path_extra_1) {
			pseudo_debug(PDBGF_OP, " %s ->", path_extra_1);
		}
		if (path) {
			pseudo_debug(PDBGF_OP, " %s", path);
		}
		/* for OP_RENAME in renameat, "fd" is also used for the
		 * second dirfd.
		 */
		if (fd != -1 && op != OP_RENAME) {
			pseudo_debug(PDBGF_OP, " [fd %d]", fd);
		}
		if (buf) {
			pseudo_debug(PDBGF_OP, " (+buf)");
			if (fd != -1) {
				pseudo_debug(PDBGF_OP, " [dev/ino: %d/%llu]",
					(int) buf->st_dev, (unsigned long long) buf->st_ino);
			}
			pseudo_debug(PDBGF_OP, " (0%o)", (int) buf->st_mode);
		}
		pseudo_debug(PDBGF_OP, ": ");
	}
	msg.type = PSEUDO_MSG_OP;
	msg.op = op;
	msg.fd = fd;
	msg.access = access;
	msg.result = RESULT_NONE;
	msg.client = getpid();

	/* do stuff */
	pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "processing request [ino %llu]\n", (unsigned long long) msg.ino);
	switch (msg.op) {
	case OP_CHDIR:
		pseudo_client_getcwd();
		do_request = 0;
		break;
	case OP_CHROOT:
		if (pseudo_client_chroot(path) == 0) {
			/* return a non-zero value to show non-failure */
			result = &msg;
		}
		do_request = 0;
		break;
	case OP_OPEN:
		pseudo_client_path(fd, path);
		/* fallthrough */
	case OP_EXEC:
		do_request = pseudo_client_logging;
		break;
	case OP_CLOSEFROM:
		/* no request needed */
		startfd = fd;
		if (pseudo_util_debug_fd > startfd)
			startfd = pseudo_util_debug_fd + 1;
		if (pseudo_localstate_dir_fd > startfd)
			startfd = pseudo_localstate_dir_fd + 1;
		if (pseudo_pwd_fd > startfd)
			startfd = pseudo_pwd_fd + 1;
		if (pseudo_grp_fd > startfd)
			startfd = pseudo_grp_fd + 1;
		if (connect_fd > startfd)
			startfd = connect_fd + 1;
		for (i = fd; i < startfd; ++i) {
			if (i == pseudo_util_debug_fd || i == pseudo_localstate_dir_fd || i == pseudo_pwd_fd ||
					i == pseudo_grp_fd || i == connect_fd)
				continue;
			pseudo_client_close(i);
			close(i);
		}
		pseudo_client_closefrom(startfd);
		/* tell the caller to close from startfd instead of fd */
		result = &msg;
		msg.fd = startfd;
		do_request = 0;
		break;
	case OP_CLOSE:
		/* no request needed */
		if (fd >= 0) {
			if (fd == connect_fd) {
				connect_fd = pseudo_fd(connect_fd, COPY_FD);
				if (connect_fd == -1) {
					pseudo_diag("tried to close connection, couldn't dup: %s\n", strerror(errno));
				}
			} else if (fd == pseudo_util_debug_fd) {
				pseudo_util_debug_fd = pseudo_fd(fd, COPY_FD);
			} else if (fd == pseudo_prefix_dir_fd) {
				pseudo_prefix_dir_fd = pseudo_fd(fd, COPY_FD);
			} else if (fd == pseudo_localstate_dir_fd) {
				pseudo_localstate_dir_fd = pseudo_fd(fd, COPY_FD);
			} else if (fd == pseudo_pwd_fd) {
				pseudo_pwd_fd = pseudo_fd(fd, COPY_FD);
				/* since we have a FILE * on it, we close that... */
				fclose(pseudo_pwd);
				/* and open a new one on the copy */
				pseudo_pwd = fdopen(pseudo_pwd_fd, "r");
			} else if (fd == pseudo_grp_fd) {
				pseudo_grp_fd = pseudo_fd(fd, COPY_FD);
				/* since we have a FILE * on it, we close that... */
				fclose(pseudo_grp);
				/* and open a new one on the copy */
				pseudo_grp = fdopen(pseudo_grp_fd, "r");
			}
		}
		pseudo_client_close(fd);
		do_request = 0;
		break;
	case OP_DUP:
		/* just copy the path over */
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "dup: fd_path(%d) = %p [%s], dup to %d\n",
			fd, fd_path(fd), fd_path(fd) ? fd_path(fd) : "<nil>",
			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:
	case OP_FCHMOD:
	case OP_MKDIR:
	case OP_MKNOD:
	case OP_SYMLINK:
		msg.uid = pseudo_fuid;
		msg.gid = pseudo_fgid;
		pseudo_debug(PDBGF_OP, "fuid: %d ", pseudo_fuid);
		/* FALLTHROUGH */
		/* chown/fchown uid/gid already calculated, and
		 * a link or rename should not change a file's ownership.
		 * (operations which can create should be CREAT or MKNOD
		 * or MKDIR)
		 */
	case OP_CHOWN:
	case OP_FCHOWN:
	case OP_FSTAT:
	case OP_LINK:
	case OP_STAT:
	case OP_CANCEL_UNLINK:
	case OP_MAY_UNLINK:
	case OP_GET_XATTR:
	case OP_LIST_XATTR:
	case OP_SET_XATTR:
	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;
	}
	/* result can only be set when PSEUDO_XATTRDB resulted in a
	 * successful store to or read from the local database or for OP_CLOSEFROM.
	 */
	if (do_request && !result) {
#ifdef PSEUDO_PROFILING
		struct timeval tv1_ipc, tv2_ipc;
#endif
                if (!pseudo_op_wait(msg.op))
                        msg.type = PSEUDO_MSG_FASTOP;
		pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending request [ino %llu]\n", (unsigned long long) msg.ino);
#ifdef PSEUDO_PROFILING
		gettimeofday(&tv1_ipc, NULL);
#endif
		if (pseudo_local_only) {
			/* disable server */
			result = NULL;
		} else {
			result = pseudo_client_request(&msg, pathlen, path);
		}
#ifdef PSEUDO_PROFILING
		gettimeofday(&tv2_ipc, NULL);
		++profile_data.messages;
		profile_data.ipc_time.tv_sec += (tv2_ipc.tv_sec - tv1_ipc.tv_sec);
		profile_data.ipc_time.tv_usec += (tv2_ipc.tv_usec - tv1_ipc.tv_usec);
#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,
					(int) result->uid,
					(int) result->gid);
			} else if (op == OP_CHMOD || op == OP_FCHMOD) {
				pseudo_debug(PDBGF_OP, " mode 0%o",
					(int) result->mode);
			} else if (op == OP_CHOWN || op == OP_FCHOWN) {
				pseudo_debug(PDBGF_OP, " uid %d:%d",
					(int) result->uid,
					(int) result->gid);
			}
		} else if (msg.type != PSEUDO_MSG_FASTOP) {
			pseudo_debug(PDBGF_OP, "(%d) no answer", getpid());
		}
	} else if (!result) {
		pseudo_debug(PDBGF_OP, "(%d) (no request)", getpid());
	}
	#ifdef PSEUDO_XATTRDB
	else {
		pseudo_debug(PDBGF_OP, "(%d) (handled through xattrdb: %s)", getpid(), pseudo_res_name(result->result));
	}
	#endif
	pseudo_debug(PDBGF_OP, "\n");

#ifdef PSEUDO_PROFILING
	gettimeofday(&tv2_op, NULL);
	profile_data.op_time.tv_sec += (tv2_op.tv_sec - tv1_op.tv_sec);
	profile_data.op_time.tv_usec += (tv2_op.tv_usec - tv1_op.tv_usec);
	if (profile_data.total_ops % profile_interval == 0) {
		pseudo_profile_report();
		if (profile_interval < 100) {
			profile_interval = profile_interval * 10;
		}
	}
#endif
	/* reenable wrappers */
	pseudo_magic();

	return result;
}

/* stuff for handling paths and execs */
static char *previous_path;
static char *previous_path_segs;
static char **path_segs;
static size_t *path_lens;

/* Makes an array of strings which are the path components
 * of previous_path.  Does this by duping the path, then replacing
 * colons with null terminators, and storing the addresses of the
 * starts of the substrings.
 */
static void
populate_path_segs(void) {
	size_t len = 0;
	char *s;
	int c = 0;

	free(path_segs);
	free(previous_path_segs);
	free(path_lens);
	path_segs = NULL;
	path_lens = NULL;
	previous_path_segs = NULL;

	if (!previous_path)
		return;

	for (s = previous_path; *s; ++s) {
		if (*s == ':')
			++c;
	}
	path_segs = malloc((c+2) * sizeof(*path_segs));
	if (!path_segs) {
		pseudo_diag("warning: failed to allocate space for %d path segments.\n",
			c);
		return;
	}
	path_lens = malloc((c + 2) * sizeof(*path_lens));
	if (!path_lens) {
		pseudo_diag("warning: failed to allocate space for %d path lengths.\n",
			c);
		free(path_segs);
		path_segs = 0;
		return;
	}
	previous_path_segs = strdup(previous_path);
	if (!previous_path_segs) {
		pseudo_diag("warning: failed to allocate space for path copy.\n");
		free(path_segs);
		path_segs = NULL;
		free(path_lens);
		path_lens = NULL;
		return;
	}
	c = 0;
	path_segs[c++] = previous_path;
	len = 0;
	for (s = previous_path; *s; ++s) {
		if (*s == ':') {
			*s = '\0';
			path_lens[c - 1] = len;
			len = 0;
			path_segs[c++] = s + 1;
		} else {
			++len;
		}
	}
	path_lens[c - 1] = len;
	path_segs[c] = NULL;
	path_lens[c] = 0;
}

const char *
pseudo_exec_path(const char *filename, int search_path) {
	char *path = getenv("PATH");
	char *candidate;
	int i;
	struct stat buf;

	if (!filename)
		return NULL;

	pseudo_antimagic();
	if (!path) {
		free(path_segs);
		free(previous_path);
		path_segs = 0;
		previous_path = 0;
	} else if (!previous_path || strcmp(previous_path, path)) {
		free(previous_path);
		previous_path = strdup(path);
		populate_path_segs();
	}

	/* absolute paths just get canonicalized. */
	if (*filename == '/') {
		candidate = pseudo_fix_path(NULL, filename, 0, 0, NULL, 0);
		pseudo_magic();
		return candidate;
	}

	if (!search_path || !path_segs) {
		candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0);
		/* executable or not, it's the only thing we can try */
		pseudo_magic();
		return candidate;
	}

	for (i = 0; path_segs[i]; ++i) {
		path = path_segs[i];
		pseudo_debug(PDBGF_CLIENT, "exec_path: checking %s for %s\n", path, filename);
		if (!*path || (*path == '.' && path_lens[i] == 1)) {
			/* empty path or . is cwd */
			candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0);
			pseudo_debug(PDBGF_CLIENT, "exec_path: in cwd, got %s\n", candidate);
		} else if (*path == '/') {
			candidate = pseudo_fix_path(path, filename, 0, path_lens[i], NULL, 0);
			pseudo_debug(PDBGF_CLIENT, "exec_path: got %s\n", candidate);
		} else {
			/* oh you jerk, making me do extra work */
			size_t len;
			char *dir = pseudo_fix_path(pseudo_cwd, path, 0, pseudo_cwd_len, &len, 0);
			if (dir) {
				candidate = pseudo_fix_path(dir, filename, 0, len, NULL, 0);
				pseudo_debug(PDBGF_CLIENT, "exec_path: got %s for non-absolute path\n", candidate);
			} else {
				pseudo_diag("couldn't allocate intermediate path.\n");
				candidate = NULL;
			}
		}
		if (candidate && !stat(candidate, &buf) && !S_ISDIR(buf.st_mode) && (buf.st_mode & 0111)) {
			pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "exec_path: %s => %s\n", filename, candidate);
			pseudo_magic();
			return candidate;
		}
	}
	/* blind guess being as good as anything */
	pseudo_magic();
	return filename;
}

