aboutsummaryrefslogtreecommitdiffstats
path: root/net/netfilter/nf_conntrack_proto_sctp.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/netfilter/nf_conntrack_proto_sctp.c')
-rw-r--r--net/netfilter/nf_conntrack_proto_sctp.c163
1 files changed, 89 insertions, 74 deletions
diff --git a/net/netfilter/nf_conntrack_proto_sctp.c b/net/netfilter/nf_conntrack_proto_sctp.c
index 7626f3e1c70a..6b2a215b2786 100644
--- a/net/netfilter/nf_conntrack_proto_sctp.c
+++ b/net/netfilter/nf_conntrack_proto_sctp.c
@@ -27,22 +27,16 @@
#include <net/netfilter/nf_conntrack_ecache.h>
#include <net/netfilter/nf_conntrack_timeout.h>
-/* FIXME: Examine ipfilter's timeouts and conntrack transitions more
- closely. They're more complex. --RR
-
- And so for me for SCTP :D -Kiran */
-
static const char *const sctp_conntrack_names[] = {
- "NONE",
- "CLOSED",
- "COOKIE_WAIT",
- "COOKIE_ECHOED",
- "ESTABLISHED",
- "SHUTDOWN_SENT",
- "SHUTDOWN_RECD",
- "SHUTDOWN_ACK_SENT",
- "HEARTBEAT_SENT",
- "HEARTBEAT_ACKED",
+ [SCTP_CONNTRACK_NONE] = "NONE",
+ [SCTP_CONNTRACK_CLOSED] = "CLOSED",
+ [SCTP_CONNTRACK_COOKIE_WAIT] = "COOKIE_WAIT",
+ [SCTP_CONNTRACK_COOKIE_ECHOED] = "COOKIE_ECHOED",
+ [SCTP_CONNTRACK_ESTABLISHED] = "ESTABLISHED",
+ [SCTP_CONNTRACK_SHUTDOWN_SENT] = "SHUTDOWN_SENT",
+ [SCTP_CONNTRACK_SHUTDOWN_RECD] = "SHUTDOWN_RECD",
+ [SCTP_CONNTRACK_SHUTDOWN_ACK_SENT] = "SHUTDOWN_ACK_SENT",
+ [SCTP_CONNTRACK_HEARTBEAT_SENT] = "HEARTBEAT_SENT",
};
#define SECS * HZ
@@ -54,12 +48,11 @@ static const unsigned int sctp_timeouts[SCTP_CONNTRACK_MAX] = {
[SCTP_CONNTRACK_CLOSED] = 10 SECS,
[SCTP_CONNTRACK_COOKIE_WAIT] = 3 SECS,
[SCTP_CONNTRACK_COOKIE_ECHOED] = 3 SECS,
- [SCTP_CONNTRACK_ESTABLISHED] = 5 DAYS,
- [SCTP_CONNTRACK_SHUTDOWN_SENT] = 300 SECS / 1000,
- [SCTP_CONNTRACK_SHUTDOWN_RECD] = 300 SECS / 1000,
+ [SCTP_CONNTRACK_ESTABLISHED] = 210 SECS,
+ [SCTP_CONNTRACK_SHUTDOWN_SENT] = 3 SECS,
+ [SCTP_CONNTRACK_SHUTDOWN_RECD] = 3 SECS,
[SCTP_CONNTRACK_SHUTDOWN_ACK_SENT] = 3 SECS,
[SCTP_CONNTRACK_HEARTBEAT_SENT] = 30 SECS,
- [SCTP_CONNTRACK_HEARTBEAT_ACKED] = 210 SECS,
};
#define SCTP_FLAG_HEARTBEAT_VTAG_FAILED 1
@@ -73,7 +66,6 @@ static const unsigned int sctp_timeouts[SCTP_CONNTRACK_MAX] = {
#define sSR SCTP_CONNTRACK_SHUTDOWN_RECD
#define sSA SCTP_CONNTRACK_SHUTDOWN_ACK_SENT
#define sHS SCTP_CONNTRACK_HEARTBEAT_SENT
-#define sHA SCTP_CONNTRACK_HEARTBEAT_ACKED
#define sIV SCTP_CONNTRACK_MAX
/*
@@ -96,9 +88,6 @@ SHUTDOWN_ACK_SENT - We have seen a SHUTDOWN_ACK chunk in the direction opposite
CLOSED - We have seen a SHUTDOWN_COMPLETE chunk in the direction of
the SHUTDOWN chunk. Connection is closed.
HEARTBEAT_SENT - We have seen a HEARTBEAT in a new flow.
-HEARTBEAT_ACKED - We have seen a HEARTBEAT-ACK in the direction opposite to
- that of the HEARTBEAT chunk. Secondary connection is
- established.
*/
/* TODO
@@ -115,33 +104,33 @@ cookie echoed to closed.
static const u8 sctp_conntracks[2][11][SCTP_CONNTRACK_MAX] = {
{
/* ORIGINAL */
-/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA */
-/* init */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCW, sHA},
-/* init_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA},
-/* abort */ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL},
-/* shutdown */ {sCL, sCL, sCW, sCE, sSS, sSS, sSR, sSA, sCL, sSS},
-/* shutdown_ack */ {sSA, sCL, sCW, sCE, sES, sSA, sSA, sSA, sSA, sHA},
-/* error */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* Can't have Stale cookie*/
-/* cookie_echo */ {sCL, sCL, sCE, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* 5.2.4 - Big TODO */
-/* cookie_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL, sHA},/* Can't come in orig dir */
-/* shutdown_comp*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sCL, sCL, sHA},
-/* heartbeat */ {sHS, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA},
-/* heartbeat_ack*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA}
+/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS */
+/* init */ {sCL, sCL, sCW, sCE, sES, sCL, sCL, sSA, sCW},
+/* init_ack */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL},
+/* abort */ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL},
+/* shutdown */ {sCL, sCL, sCW, sCE, sSS, sSS, sSR, sSA, sCL},
+/* shutdown_ack */ {sSA, sCL, sCW, sCE, sES, sSA, sSA, sSA, sSA},
+/* error */ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sCL},/* Can't have Stale cookie*/
+/* cookie_echo */ {sCL, sCL, sCE, sCE, sES, sSS, sSR, sSA, sCL},/* 5.2.4 - Big TODO */
+/* cookie_ack */ {sCL, sCL, sCW, sES, sES, sSS, sSR, sSA, sCL},/* Can't come in orig dir */
+/* shutdown_comp*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sCL, sCL},
+/* heartbeat */ {sHS, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS},
+/* heartbeat_ack*/ {sCL, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS},
},
{
/* REPLY */
-/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA */
-/* init */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA},/* INIT in sCL Big TODO */
-/* init_ack */ {sIV, sCW, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA},
-/* abort */ {sIV, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sIV, sCL},
-/* shutdown */ {sIV, sCL, sCW, sCE, sSR, sSS, sSR, sSA, sIV, sSR},
-/* shutdown_ack */ {sIV, sCL, sCW, sCE, sES, sSA, sSA, sSA, sIV, sHA},
-/* error */ {sIV, sCL, sCW, sCL, sES, sSS, sSR, sSA, sIV, sHA},
-/* cookie_echo */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV, sHA},/* Can't come in reply dir */
-/* cookie_ack */ {sIV, sCL, sCW, sES, sES, sSS, sSR, sSA, sIV, sHA},
-/* shutdown_comp*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sCL, sIV, sHA},
-/* heartbeat */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS, sHA},
-/* heartbeat_ack*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHA, sHA}
+/* sNO, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS */
+/* init */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sIV},/* INIT in sCL Big TODO */
+/* init_ack */ {sIV, sCW, sCW, sCE, sES, sSS, sSR, sSA, sIV},
+/* abort */ {sIV, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sIV},
+/* shutdown */ {sIV, sCL, sCW, sCE, sSR, sSS, sSR, sSA, sIV},
+/* shutdown_ack */ {sIV, sCL, sCW, sCE, sES, sSA, sSA, sSA, sIV},
+/* error */ {sIV, sCL, sCW, sCL, sES, sSS, sSR, sSA, sIV},
+/* cookie_echo */ {sIV, sCL, sCE, sCE, sES, sSS, sSR, sSA, sIV},/* Can't come in reply dir */
+/* cookie_ack */ {sIV, sCL, sCW, sES, sES, sSS, sSR, sSA, sIV},
+/* shutdown_comp*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sCL, sIV},
+/* heartbeat */ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sHS},
+/* heartbeat_ack*/ {sIV, sCL, sCW, sCE, sES, sSS, sSR, sSA, sES},
}
};
@@ -310,7 +299,7 @@ sctp_new(struct nf_conn *ct, const struct sk_buff *skb,
pr_debug("Setting vtag %x for secondary conntrack\n",
sh->vtag);
ct->proto.sctp.vtag[IP_CT_DIR_ORIGINAL] = sh->vtag;
- } else {
+ } else if (sch->type == SCTP_CID_SHUTDOWN_ACK) {
/* If it is a shutdown ack OOTB packet, we expect a return
shutdown complete, otherwise an ABORT Sec 8.4 (5) and (8) */
pr_debug("Setting vtag %x for new conn OOTB\n",
@@ -412,24 +401,34 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
for_each_sctp_chunk (skb, sch, _sch, offset, dataoff, count) {
/* Special cases of Verification tag check (Sec 8.5.1) */
if (sch->type == SCTP_CID_INIT) {
- /* Sec 8.5.1 (A) */
+ /* (A) vtag MUST be zero */
if (sh->vtag != 0)
goto out_unlock;
} else if (sch->type == SCTP_CID_ABORT) {
- /* Sec 8.5.1 (B) */
- if (sh->vtag != ct->proto.sctp.vtag[dir] &&
- sh->vtag != ct->proto.sctp.vtag[!dir])
+ /* (B) vtag MUST match own vtag if T flag is unset OR
+ * MUST match peer's vtag if T flag is set
+ */
+ if ((!(sch->flags & SCTP_CHUNK_FLAG_T) &&
+ sh->vtag != ct->proto.sctp.vtag[dir]) ||
+ ((sch->flags & SCTP_CHUNK_FLAG_T) &&
+ sh->vtag != ct->proto.sctp.vtag[!dir]))
goto out_unlock;
} else if (sch->type == SCTP_CID_SHUTDOWN_COMPLETE) {
- /* Sec 8.5.1 (C) */
- if (sh->vtag != ct->proto.sctp.vtag[dir] &&
- sh->vtag != ct->proto.sctp.vtag[!dir] &&
- sch->flags & SCTP_CHUNK_FLAG_T)
+ /* (C) vtag MUST match own vtag if T flag is unset OR
+ * MUST match peer's vtag if T flag is set
+ */
+ if ((!(sch->flags & SCTP_CHUNK_FLAG_T) &&
+ sh->vtag != ct->proto.sctp.vtag[dir]) ||
+ ((sch->flags & SCTP_CHUNK_FLAG_T) &&
+ sh->vtag != ct->proto.sctp.vtag[!dir]))
goto out_unlock;
} else if (sch->type == SCTP_CID_COOKIE_ECHO) {
- /* Sec 8.5.1 (D) */
+ /* (D) vtag must be same as init_vtag as found in INIT_ACK */
if (sh->vtag != ct->proto.sctp.vtag[dir])
goto out_unlock;
+ } else if (sch->type == SCTP_CID_COOKIE_ACK) {
+ ct->proto.sctp.init[dir] = 0;
+ ct->proto.sctp.init[!dir] = 0;
} else if (sch->type == SCTP_CID_HEARTBEAT) {
if (ct->proto.sctp.vtag[dir] == 0) {
pr_debug("Setting %d vtag %x for dir %d\n", sch->type, sh->vtag, dir);
@@ -478,16 +477,18 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
}
/* If it is an INIT or an INIT ACK note down the vtag */
- if (sch->type == SCTP_CID_INIT ||
- sch->type == SCTP_CID_INIT_ACK) {
- struct sctp_inithdr _inithdr, *ih;
+ if (sch->type == SCTP_CID_INIT) {
+ struct sctp_inithdr _ih, *ih;
- ih = skb_header_pointer(skb, offset + sizeof(_sch),
- sizeof(_inithdr), &_inithdr);
- if (ih == NULL)
+ ih = skb_header_pointer(skb, offset + sizeof(_sch), sizeof(*ih), &_ih);
+ if (!ih)
goto out_unlock;
- pr_debug("Setting vtag %x for dir %d\n",
- ih->init_tag, !dir);
+
+ if (ct->proto.sctp.init[dir] && ct->proto.sctp.init[!dir])
+ ct->proto.sctp.init[!dir] = 0;
+ ct->proto.sctp.init[dir] = 1;
+
+ pr_debug("Setting vtag %x for dir %d\n", ih->init_tag, !dir);
ct->proto.sctp.vtag[!dir] = ih->init_tag;
/* don't renew timeout on init retransmit so
@@ -498,11 +499,33 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
old_state == SCTP_CONNTRACK_CLOSED &&
nf_ct_is_confirmed(ct))
ignore = true;
+ } else if (sch->type == SCTP_CID_INIT_ACK) {
+ struct sctp_inithdr _ih, *ih;
+ __be32 vtag;
+
+ ih = skb_header_pointer(skb, offset + sizeof(_sch), sizeof(*ih), &_ih);
+ if (!ih)
+ goto out_unlock;
+
+ vtag = ct->proto.sctp.vtag[!dir];
+ if (!ct->proto.sctp.init[!dir] && vtag && vtag != ih->init_tag)
+ goto out_unlock;
+ /* collision */
+ if (ct->proto.sctp.init[dir] && ct->proto.sctp.init[!dir] &&
+ vtag != ih->init_tag)
+ goto out_unlock;
+
+ pr_debug("Setting vtag %x for dir %d\n", ih->init_tag, !dir);
+ ct->proto.sctp.vtag[!dir] = ih->init_tag;
}
ct->proto.sctp.state = new_state;
- if (old_state != new_state)
+ if (old_state != new_state) {
nf_conntrack_event_cache(IPCT_PROTOINFO, ct);
+ if (new_state == SCTP_CONNTRACK_ESTABLISHED &&
+ !test_and_set_bit(IPS_ASSURED_BIT, &ct->status))
+ nf_conntrack_event_cache(IPCT_ASSURED, ct);
+ }
}
spin_unlock_bh(&ct->lock);
@@ -516,14 +539,6 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
nf_ct_refresh_acct(ct, ctinfo, skb, timeouts[new_state]);
- if (old_state == SCTP_CONNTRACK_COOKIE_ECHOED &&
- dir == IP_CT_DIR_REPLY &&
- new_state == SCTP_CONNTRACK_ESTABLISHED) {
- pr_debug("Setting assured bit\n");
- set_bit(IPS_ASSURED_BIT, &ct->status);
- nf_conntrack_event_cache(IPCT_ASSURED, ct);
- }
-
return NF_ACCEPT;
out_unlock:
/* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 * pseudo_util.c, miscellaneous utility functions
 *
 * Copyright (c) 2008-2013 Wind River Systems, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License version 2.1 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Lesser GNU General Public License for more details.
 *
 * You should have received a copy of the Lesser GNU General Public License
 * version 2.1 along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 *
 */
/* we need access to RTLD_NEXT for a horrible workaround */
#define _GNU_SOURCE

#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <regex.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>

/* see the comments below about (*real_regcomp)() */
#include <dlfcn.h>

#include "pseudo.h"
#include "pseudo_ipc.h"
#include "pseudo_db.h"

struct pseudo_variables {
	char *key;
	size_t key_len;
	char *value;
};

/* The order below is not arbitrary, but based on an assumption
 * of how often things will be used.
 */
static struct pseudo_variables pseudo_env[] = {
	{ "PSEUDO_PREFIX", 13, NULL },
	{ "PSEUDO_BINDIR", 13, NULL },
	{ "PSEUDO_LIBDIR", 13, NULL },
	{ "PSEUDO_LOCALSTATEDIR", 20, NULL },
	{ "PSEUDO_PASSWD", 13, NULL },
	{ "PSEUDO_CHROOT", 13, NULL },
	{ "PSEUDO_UIDS", 11, NULL },
	{ "PSEUDO_GIDS", 11, NULL },
	{ "PSEUDO_OPTS", 11, NULL },
	{ "PSEUDO_DEBUG", 12, NULL },
	{ "PSEUDO_DEBUG_FILE", 17, NULL },
	{ "PSEUDO_TAG", 10, NULL },
	{ "PSEUDO_ENOSYS_ABORT", 19, NULL },
	{ "PSEUDO_NOSYMLINKEXP", 19, NULL },
	{ "PSEUDO_DISABLED", 15, NULL },
	{ "PSEUDO_UNLOAD", 13, NULL },
	{ "PSEUDO_ALLOW_FSYNC", 18, NULL },
#ifdef PSEUDO_PROFILING
	{ "PSEUDO_PROFILE_PATH", 19, NULL },
#endif
	{ NULL, 0, NULL } /* Magic terminator */
};

/* -1 - init hasn't been run yet
 * 0 - init has been run
 * 1 - init is running
 *
 * There are cases where the constructor is run AFTER the
 * program starts playing with things, so we need to do our
 * best to handle that case.
 */
static int pseudo_util_initted = -1;  /* Not yet run */

/* bypass wrapper logic on path computations */
int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf) = NULL;

#if 0
static void
dump_env(char **envp) {
	size_t i = 0;
	for (i = 0; envp[i]; i++) {
		pseudo_debug(PDBGF_ENV, "dump_envp: [%d]%s\n", (int) i, envp[i]);
	}

	for (i = 0; pseudo_env[i].key; i++) {
		pseudo_debug(PDBGF_ENV, "dump_envp: {%d}%s=%s\n", (int) i, pseudo_env[i].key, pseudo_env[i].value);
	}

	pseudo_debug(PDBGF_ENV, "dump_envp: _in_init %d\n", pseudo_util_initted);
}
#endif

int
pseudo_has_unload(char * const *envp) {
	static const char unload[] = "PSEUDO_UNLOAD";
	static size_t unload_len = sizeof(unload) - 1;
	size_t i = 0;

	/* Is it in the caller environment? */
	if (NULL != getenv(unload))
		return 1;

	/* Is it in the environment cache? */
	if (pseudo_util_initted == -1)
		pseudo_init_util();
	while (pseudo_env[i].key && strcmp(pseudo_env[i].key, unload))
	       ++i;
	if (pseudo_env[i].key && pseudo_env[i].value)
		return 1;

	/* Is it in the operational environment? */
	while (envp && *envp) {
		if ((!strncmp(*envp, unload, unload_len)) && ('=' == (*envp)[unload_len]))
			return 1;
		++envp;
	}
	return 0;
}

/* Caller must free memory! */
char *
pseudo_get_value(const char *key) {
	size_t i = 0;
	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++)
		;

	/* Check if the environment has it and we don't ...
	 * if so, something went wrong... so we'll attempt to recover
	 */
	if (pseudo_env[i].key && !pseudo_env[i].value && getenv(pseudo_env[i].key))
		pseudo_init_util();

	if (pseudo_env[i].value)
		value = strdup(pseudo_env[i].value);
	else
		value = NULL;

	if (!pseudo_env[i].key) 
		pseudo_diag("Unknown variable %s.\n", key);

	return value;
}

/* We make a copy, so the original values should be freed. */
int
pseudo_set_value(const char *key, const char *value) {
	int rc = 0;
	size_t i = 0;

	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++)
		;

	if (pseudo_env[i].key) {
		if (pseudo_env[i].value)
			free(pseudo_env[i].value);
		if (value) {
			char *new = strdup(value);
			if (new)
				pseudo_env[i].value = new;
			else
				pseudo_diag("warning: failed to save new value (%s) for key %s\n",
					value, key);
		} else
			pseudo_env[i].value = NULL;
	} else {
		if (!pseudo_util_initted) pseudo_diag("Unknown variable %s.\n", key);
		rc = -EINVAL;
	}

	return rc;
}

void
pseudo_init_util(void) {
	size_t i = 0;
	char * env;

	pseudo_util_initted = 1;

	for (i = 0; pseudo_env[i].key; i++) {
		if (getenv(pseudo_env[i].key))
			pseudo_set_value(pseudo_env[i].key, getenv(pseudo_env[i].key));
	}

	pseudo_util_initted = 0;

	/* Somewhere we have to set the debug level.. */
	env = pseudo_get_value("PSEUDO_DEBUG");
        if (env) {
		int i;
                int level = atoi(env);
		if (level > 0) {
			for (i = 0; i < level; ++i) {
				pseudo_debug_verbose();
			}
		} else {
			pseudo_debug_set(env);
		}
		pseudo_debug_flags_finalize();
        }
        free(env);
}

unsigned long pseudo_util_debug_flags = 0;
int pseudo_util_debug_fd = 2;
static int debugged_newline = 1;
static char pid_text[32];
static size_t pid_len;
static int pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this);
static int pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *elements, size_t elen, int leave_last);
extern char **environ;
static ssize_t pseudo_max_pathlen = -1;
static ssize_t pseudo_sys_max_pathlen = -1;

/* in our installed system, we usually use a name of the form
 * libpseudoCHECKSUM.so, where CHECKSUM is an md5 checksum of the host
 * libc.so -- this forces rebuilds of the library when the C library
 * changes.  The problem is that the pseudo binary may be
 * a prebuilt, in which case it doesn't know about CHECKSUM, so it
 * has to determine whether a given PRELINK_LIBRARIES contains libpseudo.so
 * or libpseudoCHECKSUM.so, without prior knowledge... Fancy!
 * 
 * We search for anything matching libpseudo*.so, where * is any
 * sequence of non-spaces (including an empty string), with either
 * the beginning of the string or a space in front of it, and either
 * the end of the string or a space after it.
 */
static char *libpseudo_name = "libpseudo.so";
/* this used to look for a "libpseudo*.so", but it turns out you can
 * specify a path even on Linux.
 */
static char *libpseudo_pattern = "(^|=| )[^ ]*libpseudo[^ ]*\\.so($| )";
static regex_t libpseudo_regex;
static int libpseudo_regex_compiled = 0;

/* Okay, so, there's a funny story behind this.  On one of the systems
 * we need to run on, /usr/bin/find happens to provide its own
 * definitions of regcomp and regexec which are INCOMPATIBLE with the
 * ones in the C library, and not only that, but which have buggy and/or
 * incompatible semantics, such that they trash elements of the pmatch
 * array.  So we do our best to call the "real" regcomp/regexec in the
 * C library.  If we can't find them, we just do our best and hope that
 * no one called us from a program with incompatible variants.
 *
 */
#if PSEUDO_PORT_LINUX
static int (*real_regcomp)(regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags);
static int (*real_regexec)(const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags);
#else
#define real_regcomp regcomp
#define real_regexec regexec
#endif /* PSEUDO_PORT_LINUX */

static int
libpseudo_regex_init(void) {
	int rc;

	if (libpseudo_regex_compiled)
		return 0;
#if PSEUDO_PORT_LINUX
	real_regcomp = dlsym(RTLD_NEXT, "regcomp");
	if (!real_regcomp)
		real_regcomp = regcomp;
	real_regexec = dlsym(RTLD_NEXT, "regexec");
	if (!real_regexec)
		real_regexec = regexec;
#endif
	rc = (*real_regcomp)(&libpseudo_regex, libpseudo_pattern, REG_EXTENDED);
	if (rc == 0)
		libpseudo_regex_compiled = 1;
	return rc;
}

/* given a space-or-colon-separated list of files, ala PRELINK_LIBRARIES,
 # return that list without any variants of libpseudo*.so.
 */
static char *
without_libpseudo(char *list) {
	regmatch_t pmatch[1];
	int counter = 0;
	int skip_start = 0;

	if (libpseudo_regex_init())
		return NULL;

	if (list[0] == '=' || list[0] == PSEUDO_LINKPATH_SEPARATOR[0])
		skip_start = 1;

	if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
		return list;
	}
	list = strdup(list);
	while (!(*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
		char *start = list + pmatch[0].rm_so;
		char *end = list + pmatch[0].rm_eo;
		/* don't copy over the space or = */
		start += skip_start;
		memmove(start, end, strlen(end) + 1);
		++counter;
		if (counter > 5) {
			pseudo_diag("Found way too many libpseudo.so in environment, giving up.\n");
			return list;
		}
	}
	return list;
}

static char *
with_libpseudo(char *list, char *libdir_path) {
	regmatch_t pmatch[1];

	if (libpseudo_regex_init())
		return NULL;
	if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
		size_t len;
#if PSEUDO_PORT_DARWIN
		/* <%s:%s/%s\0> */
		len = strlen(list) + 1 + strlen(libdir_path) + 1 + strlen(libpseudo_name) + 1;
#else
		/* suppress warning */
		(void) libdir_path;
		/* <%s %s\0> */
		len = strlen(list) + 1 + strlen(libpseudo_name) + 1;
#endif
		char *new = malloc(len);
		if (new) {
			/* insert space only if there were previous bits */
			/* on Darwin, we have to provide the full path to
			 * libpseudo
			 */
#if PSEUDO_PORT_DARWIN
			snprintf(new, len, "%s%s%s/%s", list,
				*list ? PSEUDO_LINKPATH_SEPARATOR : "",
				libdir_path ? libdir_path : "",
				libpseudo_name);
#else
			snprintf(new, len, "%s%s%s", list,
				*list ? PSEUDO_LINKPATH_SEPARATOR : "",
				libpseudo_name);
#endif
		}
		return new;
	} else {
		return strdup(list);
	}
}

char *pseudo_version = PSEUDO_VERSION;

/* going away soon */
static int max_debug_level = 0;

void
pseudo_debug_terse(void) {
	char s[2] = { pseudo_debug_type_symbolic(max_debug_level) };

	if (max_debug_level > 0) {
		--max_debug_level;
		pseudo_debug_clear(s);
	}
}

void
pseudo_debug_verbose(void) {
	char s[2] = { pseudo_debug_type_symbolic(max_debug_level + 1) };

	if (s[0]) {
		pseudo_debug_set(s);
		++max_debug_level;
	}
}

/* This exists because we don't want to allocate a bunch of strings
 * and free them immediately if you have several flags set.
 */
void
pseudo_debug_flags_finalize(void) {
	char buf[PDBG_MAX + 1] = "", *s = buf;
	for (int i = 0; i < PDBG_MAX; ++i) {
		if (pseudo_util_debug_flags & (1 << i)) {
			*s++ = pseudo_debug_type_symbolic(i);
		}
	}
	pseudo_set_value("PSEUDO_DEBUG", buf);
}

void
pseudo_debug_set(char *s) {
	if (!s)
		return;
	for (; *s; ++s) {
		int id = pseudo_debug_type_symbolic_id(*s);
		if (id > 0) {
			pseudo_util_debug_flags |= (1 << id);
		}
	}
}

void
pseudo_debug_clear(char *s) {
	if (!s)
		return;
	for (; *s; ++s) {
		int id = pseudo_debug_type_symbolic_id(*s);
		if (id > 0) {
			pseudo_util_debug_flags &= ~(1 << id);
		}
	}
}

int
pseudo_diag(char *fmt, ...) {
	va_list ap;
	char debuff[8192];
	int len;
	/* gcc on Ubuntu 8.10 requires that you examine the return from
	 * write(), and won't let you cast it to void.  Of course, if you
	 * can't print error messages, there's nothing to do.
	 */
	int wrote = 0;

	va_start(ap, fmt);
	len = vsnprintf(debuff, 8192, fmt, ap);
	va_end(ap);

	if (len > 8192)
		len = 8192;

	if (debugged_newline && (pseudo_util_debug_flags & PDBGF_PID)) {
		wrote += write(pseudo_util_debug_fd, pid_text, pid_len);
	}
	debugged_newline = (debuff[len - 1] == '\n');

	wrote += write(pseudo_util_debug_fd, debuff, len);
	return wrote;
}

/* store pid in text form for prepending to messages */
void
pseudo_new_pid() {
	int pid = getpid();
	pid_len = snprintf(pid_text, 32, "%d: ", pid);
	pseudo_debug(PDBGF_PID, "new pid: %d\n", pid);
}

/* helper function for pseudo_fix_path
 * adds "element" to "newpath" at location current, if it can, then
 * checks whether this now points to a symlink.  If it does, expand
 * the symlink, appending each element in turn the same way.
 */
static int
pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this) {
	static int link_recursion = 0;
	size_t curlen;
	char *current;
	PSEUDO_STATBUF buf;
	if (!newpath ||
	    !pcurrent || !*pcurrent ||
	    !root || !element) {
		pseudo_diag("pseudo_append_element: invalid args.\n");
		return -1;
	}
	current = *pcurrent;
	/* sanity-check:  ignore // or /./ */
	if (elen == 0 || (elen == 1 && *element == '.')) {
		return 1;
	}
	/* backtrack for .. */
	if (elen == 2 && element[0] == '.' && element[1] == '.') {
		/* if newpath's whole contents are '/', do nothing */
		if (current <= root + 1)
			return 1;
		/* backtrack to the character before the / */
		current -= 2;
		/* now find the previous slash */
		while (current > root && *current != '/') {
			--current;
		}
		/* and point to the nul just past it */
		*(++current) = '\0';
		*pcurrent = current;
		return 1;
	}
	curlen = current - newpath;
	/* current length, plus / <element> / \0 */
	/* => curlen + elen + 3 */
	if (curlen + elen + 3 > allocated) {
		pseudo_diag("pseudo_append_element: path too long (wanted %lu bytes).\n", (unsigned long) curlen + elen + 3);
		return -1;
	}
	memcpy(current, element, elen);
	current += elen;
	/* nul-terminate, and we now point to the nul after the slash */
	*current = '\0';
	/* now, the moment of truth... is that a symlink? */
	/* if lstat fails, that's fine -- nonexistent files aren't symlinks */
	if (!leave_this) {
		int is_link;
		is_link = (pseudo_real_lstat) && (pseudo_real_lstat(newpath, &buf) != -1) && S_ISLNK(buf.st_mode);
		if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) {
			pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath);
			is_link = 0;
		}
		if (is_link) {
			char linkbuf[pseudo_path_max() + 1];
			ssize_t linklen;
			int retval;

			linklen = readlink(newpath, linkbuf, pseudo_path_max());
			if (linklen == -1) {
				pseudo_diag("uh-oh!  '%s' seems to be a symlink, but I can't read it.  Ignoring.", newpath);
				return 0;
			}
			/* null-terminate buffer */
			linkbuf[linklen] = '\0';
			/* absolute symlink means start over! */
			if (*linkbuf == '/') {
				current = newpath + 1;
			} else {
				/* point back at the end of the previous path... */
				current -= elen;
			}
			/* null terminate at the new pointer */
			*current = '\0';
			/* append all the elements in series */
			*pcurrent = current;
			++link_recursion;
			retval = pseudo_append_elements(newpath, root, allocated, pcurrent, linkbuf, linklen, 0);
			--link_recursion;
			return retval;
		}
	}
	/* okay, not a symlink, go ahead and append a slash */
	*(current++) = '/';
	*current = '\0';
	*pcurrent = current;
	return 1;
}

static int
pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *element, size_t elen, int leave_last) {
	int retval = 1;
	const char * start = element;
	if (!newpath || !root ||
	    !current || !*current ||
	    !element) {
		pseudo_diag("pseudo_append_elements: invalid arguments.");
		return -1;
	}
	while (element < (start + elen) && *element) {
		size_t this_elen;
		int leave_this = 0;
		char *next = strchr(element, '/');
		if (!next) {
			next = strchr(element, '\0');
			leave_this = leave_last;
		}
		this_elen = next - element;
		switch (this_elen) {
		case 0: /* path => '/' */
			break;
		case 1: /* path => '?/' */
			if (*element != '.') {
				if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
					retval = -1;
				}
			}
			break;
		default:
			if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
				retval = -1;
			}
			break;
		}
		/* and now move past the separator */
		element += this_elen + 1;
	}
	return retval;
}

/* don't do so many allocations */
#define PATHBUFS 16
static char *pathbufs[PATHBUFS] = { 0 };
static int pathbuf = 0;

/* Canonicalize path.  "base", if present, is an already-canonicalized
 * path of baselen characters, presumed not to end in a /.  path is
 * the new path to be canonicalized.  The tricky part is that path may
 * contain symlinks, which must be resolved.
 * if "path" starts with a /, then it is an absolute path, and
 * we ignore base.
 */
char *
pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t baselen, size_t *lenp, int leave_last) {
	size_t newpathlen, pathlen;
	char *newpath;
	char *current;
	char *effective_root;
	int trailing_slash = 0;
	
	if (!path) {
		pseudo_diag("can't fix empty path.\n");
		return 0;
	}
	newpathlen = pseudo_path_max();
	if (!pathbufs[pathbuf]) {
		pathbufs[pathbuf] = malloc(newpathlen);
	}
	newpath = pathbufs[pathbuf];
	pathbuf = (pathbuf + 1) % PATHBUFS;
	pathlen = strlen(path);
	/* a trailing slash has special meaning */
	if (pathlen > 0 && path[pathlen - 1] == '/') {
		trailing_slash = 1;
	}
	/* allow a bit of slush.  overallocating a bit won't
	 * hurt.  rounding to 256's in the hopes that it makes life
	 * easier for the library.
	 */
	if (!newpath) {
		pseudo_diag("allocation failed seeking memory for path (%s).\n", path);
		return 0;
	}
	newpath[0] = '\0';
	current = newpath;
	if (baselen && (path[0] != '/' || rootlen)) {
		memcpy(current, base, baselen);
		current += baselen;
	}
	/* "root" is a pointer to the beginning of the *modifiable*
	 * part of the string; you can't back up over it.
	 */
	effective_root = newpath + rootlen;
	*current++ = '/';
	*current = '\0';
	/* at any given point:
	 * current points to just after the last / of newpath
	 * path points to the next element of path
	 * newpathlen is the total allocated length of newpath
	 * (current - newpath) is the used length of newpath
	 */
	if (pseudo_append_elements(newpath, effective_root, newpathlen, &current, path, pathlen, leave_last) != -1) {
		--current;
		if (*current == '/' && current > effective_root && !trailing_slash) {
			*current = '\0';
		}
		pseudo_debug(PDBGF_PATH, "%s + %s => <%s>\n",
			base ? base : "<nil>",
			path ? path : "<nil>",
			newpath ? newpath : "<nil>");
		if (lenp) {
			*lenp = current - newpath;
		}
		return newpath;
	} else {
		return 0;
	}
}

/* remove the libpseudo stuff from the environment (leaving other preloads
 * alone).
 * There's an implicit memory leak here, but this is called only right
 * before an exec(), or at most once in a given run.
 *
 * we don't try to fix the library path.
 */
void pseudo_dropenv() {
	char *ld_preload = getenv(PRELINK_LIBRARIES);

	if (ld_preload) {
		ld_preload = without_libpseudo(ld_preload);
		if (!ld_preload) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
		}
		if (ld_preload && strlen(ld_preload)) {
			pseudo_diag("ld_preload without: <%s>\n", ld_preload);
			setenv(PRELINK_LIBRARIES, ld_preload, 1);
		} else {
			unsetenv(PRELINK_LIBRARIES);
		}
	}
}

char **
pseudo_dropenvp(char * const *envp) {
	char **new_envp;
	int i, j;

	for (i = 0; envp[i]; ++i) ;

	new_envp = malloc((i + 1) * sizeof(*new_envp));
	if (!new_envp) {
		pseudo_diag("fatal: can't allocate new environment.\n");
		return NULL;
	}

	j = 0;
	for (i = 0; envp[i]; ++i) {
		if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) {
			char *new_val = without_libpseudo(envp[i]);
			if (!new_val) {
				pseudo_diag("fatal: can't allocate new environment variable.\n");
				return 0;
			} else {
				/* don't keep an empty value; if the whole string is
				 * PRELINK_LIRBARIES=, we just drop it. */
				if (strcmp(new_val, PRELINK_LIBRARIES "=")) {
					new_envp[j++] = new_val;
				}
			}
		} else {
			new_envp[j++] = envp[i];
		}
	}
	new_envp[j++] = NULL;
	return new_envp;
}

/* add pseudo stuff to the environment.
 */
void
pseudo_setupenv() {
	size_t i = 0;

	pseudo_debug(PDBGF_CLIENT, "setting up pseudo environment.\n");

	/* Make sure everything has been evaluated */
	free(pseudo_get_prefix(NULL));
	free(pseudo_get_bindir());
	free(pseudo_get_libdir());
	free(pseudo_get_localstatedir());

        while (pseudo_env[i].key) {
		if (pseudo_env[i].value) {
			setenv(pseudo_env[i].key, pseudo_env[i].value, 0);
			pseudo_debug(PDBGF_ENV | PDBGF_VERBOSE, "pseudo_env: %s => %s\n",
				pseudo_env[i].key, pseudo_env[i].value);
		}
                i++;
        }

	const char *ld_library_path = getenv(PRELINK_PATH);
	char *libdir_path = pseudo_libdir_path(NULL);
	if (!ld_library_path) {
		size_t len = strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
		char *newenv = malloc(len);
		if (!newenv) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
		}
		snprintf(newenv, len, "%s:%s64", libdir_path, libdir_path);
		setenv(PRELINK_PATH, newenv, 1);
	} 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);
		if (!newenv) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
		}
		snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path);
		setenv(PRELINK_PATH, newenv, 1);
	} else {
		/* nothing to do, ld_library_path exists and contains
		 * our preferred path */
	}

	char *ld_preload = getenv(PRELINK_LIBRARIES);
	if (ld_preload) {
		ld_preload = with_libpseudo(ld_preload, libdir_path);
		if (!ld_preload) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
		}
		setenv(PRELINK_LIBRARIES, ld_preload, 1);
		free(ld_preload);
	} else {
		ld_preload = with_libpseudo("", libdir_path);
		if (!ld_preload) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
		}
		setenv(PRELINK_LIBRARIES, ld_preload, 1);
		free(ld_preload);
	}

	/* we kept libdir path until now because with_libpseudo might
	 * need it
	 */
	free(libdir_path);


#if PSEUDO_PORT_DARWIN
	char *force_flat = getenv("DYLD_FORCE_FLAT_NAMESPACE");
	if (!force_flat) {
		setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);
	}
#endif
}

/* add pseudo stuff to the environment.
 * We can't just use setenv(), because one use case is that we're trying
 * to modify the environment of a process about to be forked through
 * execve().
 */
char **
pseudo_setupenvp(char * const *envp) {
	char **new_envp;

	size_t i, j, k;
	size_t env_count = 0;

	size_t size_pseudoenv = 0;

	char *ld_preload = NULL, *ld_library_path = NULL;

	pseudo_debug(PDBGF_ENV, "setting up envp environment.\n");

	/* Make sure everything has been evaluated */
	free(pseudo_get_prefix(NULL));
	free(pseudo_get_bindir());
	free(pseudo_get_libdir());
	free(pseudo_get_localstatedir());

	for (i = 0; envp[i]; ++i) {
		if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) {
			ld_preload = envp[i];
		}
		if (STARTSWITH(envp[i], PRELINK_PATH "=")) {
			ld_library_path = envp[i];
		}
		++env_count;
	}

        for (i = 0; pseudo_env[i].key; i++) {
		size_pseudoenv++;
        }

	env_count += size_pseudoenv; /* We're going to over allocate */

	j = 0;
	new_envp = malloc((env_count + 1) * sizeof(*new_envp));
	if (!new_envp) {
		pseudo_diag("fatal: can't allocate new environment.\n");
		return NULL;
	}	

	char *libdir_path = pseudo_libdir_path(NULL);
	if (!ld_library_path) {
		size_t len = strlen(PRELINK_PATH "=") + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
		char *newenv = malloc(len);
		if (!newenv) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
		}
		snprintf(newenv, len, PRELINK_PATH "=%s:%s64", libdir_path, libdir_path);
		new_envp[j++] = 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);
		if (!newenv) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
		}
		snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path);
		new_envp[j++] = newenv;
	} else {
		/* keep old value */
		new_envp[j++] = ld_library_path;
	}

	if (ld_preload) {
		ld_preload = with_libpseudo(ld_preload, libdir_path);
		if (!ld_preload) {
			pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
		}
		new_envp[j++] = ld_preload;
	} else {
		ld_preload = with_libpseudo("", libdir_path);
		size_t len = strlen(PRELINK_LIBRARIES "=") + strlen(ld_preload) + 1;
		char *newenv = malloc(len);
		snprintf(newenv, len, PRELINK_LIBRARIES "=%s", ld_preload);
		new_envp[j++] = newenv;
		free(ld_preload);
	}

	free(libdir_path);

	for (i = 0; envp[i]; ++i) {
		if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) continue;
		if (STARTSWITH(envp[i], PRELINK_PATH "=")) continue;
		new_envp[j++] = envp[i];
	}

	for (i = 0; pseudo_env[i].key; i++) {
		int found = 0;
		for (k = 0; k < j; k++) {
			if (!strncmp(pseudo_env[i].key,new_envp[k],strlen(pseudo_env[i].key))) {
				found = 1;
				break;
			}
		}
		if (!found && pseudo_env[i].key && pseudo_env[i].value) {
			size_t len = strlen(pseudo_env[i].key) + 1 + strlen(pseudo_env[i].value) + 1;
			char *newenv = malloc(len);
			if (!newenv) {
				pseudo_diag("fatal: can't allocate new variable.\n");
			}
			snprintf(newenv, len, "%s=%s", pseudo_env[i].key, pseudo_env[i].value);
			new_envp[j++] = newenv;
		}
	}
	new_envp[j++] = NULL;
	return new_envp;
}

/* Append the file value to the prefix value. */
char *
pseudo_append_path(const char * prefix, size_t prefix_len, char *file) {
	char *path;

	if (!file) {
		return strdup(prefix);
	} else {
		size_t len = prefix_len + strlen(file) + 2;
		path = malloc(len);
		if (path) {
			char *endptr;
			int rc;

			rc = snprintf(path, len, "%s", prefix);
			/* this certainly SHOULD be impossible */
			if ((size_t) rc >= len)
				rc = len - 1;
			endptr = path + rc;
			/* strip extra slashes.
			 * This probably has no real effect, but I don't like
			 * seeing "//" in paths.
			 */
			while ((endptr > path) && (endptr[-1] == '/'))
				--endptr;
			snprintf(endptr, len - (endptr - path), "/%s", file);
		}
		return path;
	}
}


/* get the full path to a file under $PSEUDO_PREFIX.  Other ways of
 * setting the prefix all set it in the environment.
 */
char *
pseudo_prefix_path(char *file) {
	char * rc;
	char * prefix = pseudo_get_prefix(NULL);

	if (!prefix) {
		pseudo_diag("You must set the PSEUDO_PREFIX environment variable to run pseudo.\n");
		exit(1);
	}

	rc = pseudo_append_path(prefix, strlen(prefix), file);	
	free(prefix);

	return rc;
}

/* get the full path to a file under $PSEUDO_BINDIR. */
char *
pseudo_bindir_path(char *file) {
	char * rc;
	char * bindir = pseudo_get_bindir();

	if (!bindir) {
		pseudo_diag("You must set the PSEUDO_BINDIR environment variable to run pseudo.\n");
		exit(1);
	}

	rc = pseudo_append_path(bindir, strlen(bindir), file);	
	free(bindir);

	return rc;
}

/* get the full path to a file under $PSEUDO_LIBDIR. */
char *
pseudo_libdir_path(char *file) {
	char * rc;
	char * libdir = pseudo_get_libdir();

	if (!libdir) {
		pseudo_diag("You must set the PSEUDO_LIBDIR environment variable to run pseudo.\n");
		exit(1);
	}

	rc = pseudo_append_path(libdir, strlen(libdir), file);	
	free(libdir);

	return rc;
}

/* get the full path to a file under $PSEUDO_LOCALSTATEDIR. */
char *
pseudo_localstatedir_path(char *file) {
	char * rc;
	char * localstatedir = pseudo_get_localstatedir();

	if (!localstatedir) {
		pseudo_diag("You must set the PSEUDO_LOCALSTATEDIR environment variable to run pseudo.\n");
		exit(1);
	}

	rc = pseudo_append_path(localstatedir, strlen(localstatedir), file);	
	free(localstatedir);

	return rc;
}

char *
pseudo_get_prefix(char *pathname) {
	char *s = pseudo_get_value("PSEUDO_PREFIX");

	/* Generate the PSEUDO_PREFIX if necessary, and possible... */
	if (!s && pathname) {
		char mypath[pseudo_path_max()];
		char *dir;
		char *tmp_path;

		if (pathname[0] == '/') {
			snprintf(mypath, pseudo_path_max(), "%s", pathname);
			s = mypath + strlen(mypath);
		} else {
			if (!getcwd(mypath, pseudo_path_max())) {
				mypath[0] = '\0';
			}
			s = mypath + strlen(mypath);
			s += snprintf(s, pseudo_path_max() - (s - mypath), "/%s",
				pathname);
		}
		tmp_path = pseudo_fix_path(NULL, mypath, 0, 0, 0, AT_SYMLINK_NOFOLLOW);
		/* point s to the end of the fixed path */
		if ((int) strlen(tmp_path) >= pseudo_path_max()) {
			pseudo_diag("Can't expand path '%s' -- expansion exceeds %d.\n",
				mypath, (int) pseudo_path_max());
		} else {
			s = mypath + snprintf(mypath, pseudo_path_max(), "%s", tmp_path);
		}

		while (s > (mypath + 1) && *s != '/')
			--s;
		*s = '\0';
		dir = s - 1;
		while (dir > mypath && *dir != '/') {
			--dir;
		}
		/* strip bin directory, if any */
		if (!strncmp(dir, "/bin", 4)) {
			*dir = '\0';
		}
		/* degenerate case: /bin/pseudo should yield a pseudo_prefix "/" */
		if (*mypath == '\0') {
			strcpy(mypath, "/");
		}

		pseudo_diag("Warning: PSEUDO_PREFIX unset, defaulting to %s.\n",
			mypath);
		pseudo_set_value("PSEUDO_PREFIX", mypath);
		s = pseudo_get_value("PSEUDO_PREFIX");
	}
	return s;
}

char *
pseudo_get_bindir(void) {
	char *s = pseudo_get_value("PSEUDO_BINDIR");
	if (!s) {
		char *pseudo_bindir = pseudo_prefix_path(PSEUDO_BINDIR);
		if (pseudo_bindir) {
			pseudo_set_value("PSEUDO_BINDIR", pseudo_bindir);
			s = pseudo_bindir;
		}
	}
	return s;
}

char *
pseudo_get_libdir(void) {
	char *s = pseudo_get_value("PSEUDO_LIBDIR");
	if (!s) {
		char *pseudo_libdir;
		pseudo_libdir = pseudo_prefix_path(PSEUDO_LIBDIR);
		if (pseudo_libdir) {
			pseudo_set_value("PSEUDO_LIBDIR", pseudo_libdir);
			s = pseudo_libdir;
		}
	}
#if PSEUDO_PORT_DARWIN
	/* on Darwin, we need lib64, because dyld won't search */
#else
	/* If we somehow got lib64 in there, clean it down to just lib... */
	if (s) {
		size_t len = strlen(s);
		if (s[len-2] == '6' && s[len-1] == '4') {
			s[len-2] = '\0';
			pseudo_set_value("PSEUDO_LIBDIR", s);
		}
	}
#endif

	return s;
}

char *
pseudo_get_localstatedir() {
	char *s = pseudo_get_value("PSEUDO_LOCALSTATEDIR");
	if (!s) {
		char *pseudo_localstatedir = pseudo_prefix_path(PSEUDO_LOCALSTATEDIR);
		if (pseudo_localstatedir) {
			pseudo_set_value("PSEUDO_LOCALSTATEDIR", pseudo_localstatedir);
			s = pseudo_localstatedir;
		}
	}
	return s;
}

/* these functions define the sizes pseudo will try to use
 * when trying to allocate space, or guess how much space
 * other people will have allocated; see the GNU man page
 * for realpath(3) for an explanation of why the sys_path_max
 * functions exists, approximately -- it's there to be a size
 * that I'm pretty sure the user will have allocated if they
 * provided a buffer to that defective function.
 */
/* I'm pretty sure this will be larger than real PATH_MAX */
#define REALLY_BIG_PATH 16384
/* A likely common value for PATH_MAX */
#define SORTA_BIG_PATH 4096
ssize_t
pseudo_path_max(void) {
	if (pseudo_max_pathlen == -1) {
		long l = pathconf("/", _PC_PATH_MAX);
		if (l < 0) {
			if (_POSIX_PATH_MAX > 0) {
				pseudo_max_pathlen = _POSIX_PATH_MAX;
			} else {
				pseudo_max_pathlen = REALLY_BIG_PATH;
			}
		} else {
			if (l <= REALLY_BIG_PATH) {
				pseudo_max_pathlen = l;
			} else {
				pseudo_max_pathlen = REALLY_BIG_PATH;
			}
		}
	}
	return pseudo_max_pathlen;
}

ssize_t
pseudo_sys_path_max(void) {
	if (pseudo_sys_max_pathlen == -1) {
		long l = pathconf("/", _PC_PATH_MAX);
		if (l < 0) {
			if (_POSIX_PATH_MAX > 0) {
				pseudo_sys_max_pathlen = _POSIX_PATH_MAX;
			} else {
				pseudo_sys_max_pathlen = SORTA_BIG_PATH;
			}
		} else {
			if (l <= SORTA_BIG_PATH) {
				pseudo_sys_max_pathlen = l;
			} else {
				pseudo_sys_max_pathlen = SORTA_BIG_PATH;
			}
		}
	}
	return pseudo_sys_max_pathlen;
}

/* complicated because in theory you can have modes like * 'ab+'
 * which is the same as 'a+' in POSIX.  The first letter really does have
 * to be one of r, w, a, though.
 */
int
pseudo_access_fopen(const char *mode) {
	int access = 0;
	for (; *mode; ++mode) {
		switch (*mode) {
		case 'a':
			access |= (PSA_APPEND | PSA_WRITE);
			break;
		case 'r':
			access |= PSA_READ;
			break;
		case 'w':
			access |= PSA_WRITE;
			break;
		case 'x':
			/* special case -- note that this conflicts with a
			 * rarely-used glibc extension
			 */
			access |= PSA_EXEC;
			break;
		case 'b':			/* binary mode */
			break;
		case 'c': case 'e': case 'm':	/* glibc extensions */
			break;
		case '+':
			/* one of these will already be set, presumably */
			access |= (PSA_READ | PSA_WRITE);
			break;
		default:
			access = -1;
			break;
		}
	}
	return access;
}

/* find a passwd/group file to use
 * uses in order:
 * - PSEUDO_CHROOT/etc/<file> (only if CHROOT is set)
 * - PSEUDO_PASSWD/etc/<file>
 * - /etc/<file>
 */

#if PSEUDO_PORT_DARWIN
/* on Darwin, you can't just use /etc/passwd for system lookups,
 * you have to use the real library calls because they know about
 * Directory Services.  So...
 *
 * We make up fake fds and FILE * objects that can't possibly be
 * valid.
 */
int pseudo_host_etc_passwd_fd = -3;
int pseudo_host_etc_group_fd = -4;
static FILE pseudo_fake_passwd_file;
static FILE pseudo_fake_group_file;
FILE *pseudo_host_etc_passwd_file = &pseudo_fake_passwd_file;
FILE *pseudo_host_etc_group_file = &pseudo_fake_group_file;

#endif

int
pseudo_etc_file(const char *file, char *realname, int flags, const char **search_dirs, int dircount) {
	char filename[pseudo_path_max()];
	int rc = -1;

	if (!file) {
		pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: needs argument, usually passwd/group\n");
		errno = ENOENT;
		return -1;
	}
	int i;
	if (!search_dirs || dircount == 0) {
		pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: no search dirs.\n");
		errno = ENOENT;
		return -1;
	}
	for (i = 0; i < dircount; ++i) {
		const char *s = search_dirs[i];
		/* we used to pass in some paths as NULL when unset,
		 * so we skipped those. Now NULL entries don't get
		 * put in, so the only NULL should be the sentinel
		 * value, and this should never get hit.
		 *
		 * "should" is not comforting to me.
		 */
		if (!s)
			break;
#if PSEUDO_PORT_DARWIN
		/* special magic: empty string implies our emulation
		 * of the passwd/group files.
		 */
		if (!*s) {
			if (!strcmp("passwd", file)) {
				pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic passwd fd\n");
				return pseudo_host_etc_passwd_fd;
			} else if (!strcmp("group", file)) {
				pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic group fd\n");
				return pseudo_host_etc_group_fd;
			}
		}
#endif
		snprintf(filename, pseudo_path_max(), "%s/etc/%s",
			s, file);
		rc = open(filename, flags, 0600);
		if (rc >= 0) {
			if (realname)
				strcpy(realname, filename);
			pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: using '%s' for '%s'.\n",
				filename, file);
			return rc;
		} else {
			pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "didn't find <%s>\n",
				filename);
		}
	}
	return rc;
}

/* set up a log file */
int
pseudo_logfile(char *defname, int prefer_fd) {
	char *pseudo_path;
	char *filename = pseudo_get_value("PSEUDO_DEBUG_FILE");
	char *s;
#if PSEUDO_PORT_LINUX
	extern char *program_invocation_short_name; /* glibcism */
#else
	char *program_invocation_short_name = "unknown";
#endif
	int fd;

	if (!filename) {
		if (!defname) {
			pseudo_debug(PDBGF_INVOKE, "no special log file requested, using stderr.\n");
			return -1;
		}
		pseudo_path = pseudo_localstatedir_path(defname);
		if (!pseudo_path) {
			pseudo_diag("can't get path for prefix/%s\n", PSEUDO_LOGFILE);
			return -1;
		}
	} else {
		char *pid = NULL, *prog = NULL;
		size_t len;
		for (s = filename; *s; ++s) {
			if (s[0] == '%') {
				switch (s[1]) {
				case '%': /* skip the %% */
					++s;
					break;
				case 'd':
					if (pid) {
						pseudo_diag("found second %%d in PSEUDO_DEBUG_FILE, ignoring.\n");
						return -1;
					} else {
						pid = s;
					}
					break;
				case 's':
					if (prog) {
						pseudo_diag("found second %%s in PSEUDO_DEBUG_FILE, ignoring.\n");
						return -1;
					} else {
						prog = s;
					}
					break;
				default:
					if (isprint(s[1])) {
						pseudo_diag("found unknown format character '%c' in PSEUDO_DEBUG_FILE, ignoring.\n",
							s[1]);
					} else {
						pseudo_diag("found unknown format character '\\x%02x' in PSEUDO_DEBUG_FILE, ignoring.\n",
							(unsigned char) s[1]);
					}
					return -1;
					break;
				}
			}
		}
		len = strlen(filename) + 1;
		if (pid)
			len += 8;
		if (prog)
			len += strlen(program_invocation_short_name);
		pseudo_path = malloc(len);
		if (!pseudo_path) {
			pseudo_diag("can't allocate space for debug file name.\n");
			return -1;
		}
		if (pid && prog) {
			if (pid < prog) {
				snprintf(pseudo_path, len, filename, getpid(), program_invocation_short_name);
			} else {
				snprintf(pseudo_path, len, filename, program_invocation_short_name, getpid());
			}
		} else if (pid) {
			snprintf(pseudo_path, len, filename, getpid());
		} else if (prog) {
			snprintf(pseudo_path, len, filename, program_invocation_short_name);
		} else {
			strcpy(pseudo_path, filename);
		}
		free(filename);
	}	
	fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT, 0644);
	if (fd == -1) {
		pseudo_diag("help: can't open log file %s: %s\n", pseudo_path, strerror(errno));
	} else {
		/* try to force fd to prefer_fd.  We do this because glibc's malloc
		 * debug unconditionally writes to fd 2, and we don't want
		 * a client process ending op on fd 2, or server debugging
		 * becomes a nightmare. So, server sets prefer_fd to 2. Client
		 * leaves it at -1.
		 */
		if (prefer_fd >= 0 && fd != prefer_fd) {
			int newfd;
			close(prefer_fd);
			newfd = dup2(fd, prefer_fd);
			if (newfd != -1) {
				fd = newfd;
			}
		}
		pseudo_util_debug_fd = fd;
	}
	free(pseudo_path);
	if (fd == -1)
		return -1;
	else
		return 0;
}

void
pseudo_stat32_from64(struct stat *buf32, const struct stat64 *buf) {
	buf32->st_dev = buf->st_dev;
	buf32->st_ino = buf->st_ino;
	buf32->st_mode = buf->st_mode;
	buf32->st_nlink = buf->st_nlink;
	buf32->st_uid = buf->st_uid;
	buf32->st_gid = buf->st_gid;
	buf32->st_rdev = buf->st_rdev;
	buf32->st_size = buf->st_size;
	buf32->st_blksize = buf->st_blksize;
	buf32->st_blocks = buf->st_blocks;
	buf32->st_atime = buf->st_atime;
	buf32->st_mtime = buf->st_mtime;
	buf32->st_ctime = buf->st_ctime;
}

void
pseudo_stat64_from32(struct stat64 *buf64, const struct stat *buf) {
	buf64->st_dev = buf->st_dev;
	buf64->st_ino = buf->st_ino;
	buf64->st_mode = buf->st_mode;
	buf64->st_nlink = buf->st_nlink;
	buf64->st_uid = buf->st_uid;
	buf64->st_gid = buf->st_gid;
	buf64->st_rdev = buf->st_rdev;
	buf64->st_size = buf->st_size;
	buf64->st_blksize = buf->st_blksize;
	buf64->st_blocks = buf->st_blocks;
	buf64->st_atime = buf->st_atime;
	buf64->st_mtime = buf->st_mtime;
	buf64->st_ctime = buf->st_ctime;
}

/* pretty-dump some data.
 * expects to be called using pseudo_debug_call() so it doesn't have
 * to do debug checks.
 */
void
pseudo_dump_data(char *name, const void *v, size_t len) {
	char hexbuf[128];
	char asciibuf[32];
	const unsigned char *base = v;
	const unsigned char *data = base;
	int remaining = len;
	pseudo_diag("%s at %p [%d byte%s]:\n",
		name ? name : "data", v, (int) len, len == 1 ? "" : "s");
	while (remaining > 0) {
		char *hexptr = hexbuf;
		char *asciiptr = asciibuf;
		for (int i = 0; i < 16 && i < remaining; ++i) {
			hexptr += snprintf(hexptr, 4, "%02x ", data[i]);
			if (isprint(data[i])) {
				*asciiptr++ = data[i];
			} else {
				*asciiptr++ = '.';
			}
			if (i % 4 == 3) {
				*hexptr++ = ' ';
			}
		}
		*hexptr = '\0';
		*asciiptr = '\0';
		pseudo_diag("0x%06x %-50.50s '%.16s'\n",
			(int) (data - base),
			hexbuf, asciibuf);
		remaining = remaining - 16;
		data = data + 16;
	}
}