tuapi.c at [9a06656e8e]

File tuapi.c artifact 4c88734079 part of check-in 9a06656e8e


#define _LINUX_SOURCE 1
#include <sys/syscall.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/swap.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <tcl.h>

#include <linux/sockios.h>
#include <linux/route.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_bridge.h>
#include <linux/loop.h>
#include <linux/fs.h>

#ifndef HOST_NAME_MAX
/* SUSv2 Limit */
#define HOST_NAME_MAX 255
#endif

/* From Linux 2.6 */
#ifndef MNT_DETACH
#define MNT_DETACH 2
#endif
#ifndef MNT_EXPIRE
#define MNT_EXPIRE 4
#endif
#ifndef MS_MOVE
#define MS_MOVE 8192
#endif

/* Simple macros */
#ifndef MAX
#define MAX(a, b) (((a) < (b)) ? (b) : (a))
#endif
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif

/* User environment, for execve */
extern char **environ;

/* Re-implement these if needed */
#ifdef SYS_init_module
static int init_module(void *val, unsigned long len, const char *args) {
	return(syscall(SYS_init_module, val, len, args));
}
#endif
#ifdef SYS_pivot_root
static int pivot_root(const char *new_root, const char *put_old) {
	return(syscall(SYS_pivot_root, new_root, put_old));
}
#endif

/*
 * Simple hash routine to enable switching on a string to be implemented
 */
static unsigned long tuapi_internal_simplehash(const void *databuf, int datalen) {
	unsigned long retval = 0;
	const unsigned char *data;

	data = databuf;

	for (; datalen > 0; datalen--,data++) {
		retval ^= (retval >> 25) & 0x7F;
		retval <<= 7;
		retval &= (0xFFFFFFFFUL);
		retval ^= *data;
	}

	return(retval);
}

static unsigned long tuapi_internal_simplehash_obj(Tcl_Obj *tcl_data) {
	unsigned long retval;
	char *data;
	int datalen = -1;

	data = Tcl_GetStringFromObj(tcl_data, &datalen);

	retval = tuapi_internal_simplehash(data, datalen);

	return(retval);
}

#if 0
/* NOTUSED: Uncomment when needed: */
static unsigned long tuapi_internal_simplehash_str(const char *data) {
	unsigned long retval;
	int datalen;

	datalen = strlen(data);

	retval = tuapi_internal_simplehash(data, datalen);

	return(retval);
}
#endif

static int tuapi_internalproc_simplehash(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	unsigned long hashval;
	Tcl_Obj *hashval_obj;

	if (objc != 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::internal::hash value\"", -1));

		return(TCL_ERROR);
	}

	hashval = tuapi_internal_simplehash_obj(objv[1]);

	hashval_obj = Tcl_NewObj();
	Tcl_SetWideIntObj(hashval_obj, hashval);

	Tcl_SetObjResult(interp, hashval_obj);

	return(TCL_OK);
}

static int tuapi_internal_getsock(int *sock_v4_out, int *sock_v6_out) {
	int sock_v4 = -1, sock_v6 = -1;
	int sock;

	if (sock_v4_out == NULL && sock_v6_out == NULL) {
		return(-1);
	}

	if (sock_v4_out != NULL) {
		/*
		 * Check for IPv4 support before trying to create an IPv4 socket to
		 * avoid demand-loading IPv4 (XXX: TODO)
		 */
		sock_v4 = socket(AF_INET, SOCK_DGRAM, 0);
	}

	if (sock_v6_out != NULL) {
		/*
		 * Check for IPv6 support before trying to create an IPv6 socket to
		 * avoid demand-loading IPv6 (XXX: TODO)
		 */
		sock_v6 = socket(AF_INET6, SOCK_DGRAM, 0);
	}

	/* Pick a socket to query for the interface list */
	if (sock_v4 == -1 && sock_v6 == -1) {
		return(-1);
	}

	if (sock_v6 != -1) {
		sock = sock_v6;
	} else {
		sock = sock_v4;
	}

	if (sock_v4_out != NULL) {
		*sock_v4_out = sock_v4;
	}

	if (sock_v6_out != NULL) {
		*sock_v6_out = sock_v6;
	}

	return(sock);
}

/*
 * Low-level System Call Wrapper Procedures
 *
 * These procedures should minimally wrap Linux or UNIX system calls to
 * expose to the Tcl-level.  Where possible accept symbolic names rather
 * than numeric values (.e.g, list of values to OR together to get flags).
 */
static int tuapi_mount(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_Obj *mountflags_obj, **mountflags_list, *mountflag;
	int mountflags_list_len;
	char *source, *target, *fstype;
	unsigned long mountflags = 0;
	void *data = NULL;
	int mount_ret, tcl_ret;

	if (objc < 5 || objc > 6) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::mount source target fstype mountflags ?data?\"", -1));

		return(TCL_ERROR);
	}

	source = Tcl_GetString(objv[1]);
	target = Tcl_GetString(objv[2]);
	fstype = Tcl_GetString(objv[3]);
	mountflags_obj = objv[4];

	if (objc == 6) {
		data = Tcl_GetString(objv[5]);
	}

	tcl_ret = Tcl_ListObjGetElements(interp, mountflags_obj, &mountflags_list_len, &mountflags_list);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	for (; mountflags_list_len > 0; mountflags_list_len--,mountflags_list++) {
		mountflag = mountflags_list[0];

		switch (tuapi_internal_simplehash_obj(mountflag)) {
#ifdef MS_BIND
			case 0x8526744: /* BIND */
				mountflags |= MS_BIND;
				break;
#endif
#ifdef MS_DIRSYNC
			case 0x2aff41c3: /* DIRSYNC */
				mountflags |= MS_DIRSYNC;
				break;
#endif
#ifdef MS_MANDLOCK
			case 0x410dbcb: /* MANDLOCK */
				mountflags |= MS_MANDLOCK;
				break;
#endif
#ifdef MS_MOVE
			case 0x9b3eb45: /* MOVE */
				mountflags |= MS_MOVE;
				break;
#endif
#ifdef MS_NOATIME
			case 0x1a0f58c5: /* NOATIME */
				mountflags |= MS_NOATIME;
				break;
#endif
#ifdef MS_NODEV
			case 0xe9f120d6: /* NODEV */
				mountflags |= MS_NODEV;
				break;
#endif
#ifdef MS_NODIRATIME
			case 0xde08ff45: /* NODIRATIME */
				mountflags |= MS_NODIRATIME;
				break;
#endif
#ifdef MS_NOEXEC
			case 0xf8b718c3: /* NOEXEC */
				mountflags |= MS_NOEXEC;
				break;
#endif
#ifdef MS_NOSUID
			case 0xfa745ec4: /* NOSUID */
				mountflags |= MS_NOSUID;
				break;
#endif
#ifdef MS_RDONLY
			case 0x49f2ec59: /* RDONLY */
				mountflags |= MS_RDONLY;
				break;
#endif
#ifdef MS_RELATIME
			case 0x481954c5: /* RELATIME */
				mountflags |= MS_RELATIME;
				break;
#endif
#ifdef MS_REMOUNT
			case 0xd9507154: /* REMOUNT */
				mountflags |= MS_REMOUNT;
				break;
#endif
#ifdef MS_SILENT
			case 0x99902954: /* SILENT */
				mountflags |= MS_SILENT;
				break;
#endif
#ifdef MS_STRICTATIME
			case 0x562fa045: /* STRICTATIME */
				mountflags |= MS_STRICTATIME;
				break;
#endif
#ifdef MS_SYNCHRONOUS
			case 0xbf799353: /* SYNCHRONOUS */
			case 0xa766743: /* SYNC */
				mountflags |= MS_SYNCHRONOUS;
				break;
#endif
			default:
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown element in mountflags: \"%s\"", Tcl_GetString(mountflag)));

				return(TCL_ERROR);
		}
	}

	mount_ret = mount(source, target, fstype, mountflags, data);
	if (mount_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	Tcl_SetObjResult(interp, Tcl_NewStringObj(target, -1));

	return(TCL_OK);
}

static int tuapi_umount(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_Obj **flags, *flag;
	Tcl_Obj *pathname_obj;
	char *pathname;
	int umount2_flags = 0;
	int flags_cnt;
	int chk_ret, tcl_ret;

	if (objc < 2 || objc > 3) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"tuapi::syscall::umount dir ?flags?\"", -1));

		return(TCL_ERROR);
	}

	pathname_obj = objv[1];
	pathname = Tcl_GetString(pathname_obj);

	/* Set a default return value */
	Tcl_SetObjResult(interp, pathname_obj);

	if (objc == 3) {
		tcl_ret = Tcl_ListObjGetElements(interp, objv[2], &flags_cnt, &flags);
		if (tcl_ret != TCL_OK) {
			return(tcl_ret);
		}

		for (; flags_cnt > 0; flags_cnt--,flags++) {
			flag = flags[0];

			switch (tuapi_internal_simplehash_obj(flag)) {
				case 0x69f4a3c5: /* FORCE */
					umount2_flags |= MNT_FORCE;

					break;
				case 0x5a9173c8: /* DETACH */
					umount2_flags |= MNT_DETACH;

					break;
				case 0x8a137fc5: /* EXPIRE */
					umount2_flags |= MNT_EXPIRE;

					break;
				default:
					Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown flag \"%s\" specified", Tcl_GetString(flag)));
	
					return(TCL_ERROR);
			}
		}

		chk_ret = umount2(pathname, umount2_flags);

		/* Do not return an error for this case, since it is apparently not exceptional */
		if (chk_ret != 0 && (umount2_flags & MNT_EXPIRE) == MNT_EXPIRE && errno == EAGAIN) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("AGAIN", -1));

			chk_ret = 0;
		}
	} else {
		chk_ret = umount(pathname);
	}

	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_swapon(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *pathname;
	int chk_ret;

	if (objc != 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"tuapi::syscall::swapon pathname\"", -1));

		return(TCL_ERROR);
	}

	pathname = Tcl_GetString(objv[1]);

	chk_ret = swapon(pathname, 0);
	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_swapoff(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *pathname;
	int chk_ret;

	if (objc != 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"tuapi::syscall::swapoff pathname\"", -1));

		return(TCL_ERROR);
	}

	pathname = Tcl_GetString(objv[1]);

	chk_ret = swapoff(pathname);
	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_insmod(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_Channel fd;
	Tcl_Obj *module_filename, *module_data;
	void *module_data_val;
	int module_data_len;
	int read_ret, chk_ret;

	if (objc < 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"tuapi::syscall::insmod filename ?args ...?\"", -1));

		return(TCL_ERROR);
	}

	module_filename = objv[1];

	fd = Tcl_FSOpenFileChannel(interp, module_filename, "r", 0600);
	if (fd == NULL) {
		return(TCL_ERROR);
	}

	chk_ret = Tcl_SetChannelOption(interp, fd, "-translation", "binary");
	if (chk_ret != TCL_OK) {
		Tcl_Close(interp, fd);

		return(chk_ret);
	}

	module_data = Tcl_NewObj();

	read_ret = Tcl_ReadChars(fd, module_data, -1, 0);

	Tcl_Close(interp, fd);

	if (read_ret <= 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("read failed", -1));

		return(TCL_ERROR);
	}

	module_data_val = Tcl_GetByteArrayFromObj(module_data, &module_data_len);

	chk_ret = init_module(module_data_val, module_data_len, "");
	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_rmmod(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_lsmod(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_hostname(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char hostname[HOST_NAME_MAX + 1];
	int chk_ret;

	if (objc == 1) {
		/* No arguments given, just return the hostname */
		chk_ret = gethostname(hostname, sizeof(hostname));
		if (chk_ret != 0) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

			return(TCL_ERROR);
		}

		hostname[sizeof(hostname) - 1] = '\0';

		Tcl_SetObjResult(interp, Tcl_NewStringObj(hostname, -1));

		return(TCL_OK);
	}

	if (objc == 2) {
		/* Exactly one argument given, set the hostname */
		strncpy(hostname, Tcl_GetString(objv[1]), sizeof(hostname));
		hostname[sizeof(hostname) - 1] = '\0';

		chk_ret = sethostname(hostname, strlen(hostname));
		if (chk_ret != 0) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

			return(TCL_ERROR);
		}

		Tcl_SetObjResult(interp, Tcl_NewStringObj(hostname, -1));

		return(TCL_OK);
	}

	Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"hostname ?hostname?\"", -1));

	return(TCL_ERROR);
}

static int tuapi_domainname(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_chroot(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *pathname;
	int chk_ret;

	if (objc != 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall:chroot pathname\"", -1));

		return(TCL_ERROR);
	}

	pathname = Tcl_GetString(objv[1]);

	chk_ret = chroot(pathname);
	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_pivot_root(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *new_root, *put_old;
	int chk_ret;

	if (objc != 3) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::pivot_root new_root put_old\"", -1));

		return(TCL_ERROR);
	}

	new_root = Tcl_GetString(objv[1]);
	put_old = Tcl_GetString(objv[2]);

	chk_ret = pivot_root(new_root, put_old);
	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_mknod(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_getuid(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_kill(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_Obj *signal_obj;

	Tcl_WideInt pid_wide, sig_wide;
	pid_t pid;
	int sig;
	int kill_ret, tcl_ret;

	if (objc != 3) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::kill pid sig\"", -1));

		return(TCL_ERROR);
	}

	tcl_ret = Tcl_GetWideIntFromObj(interp, objv[1], &pid_wide);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}
	pid = pid_wide;

	signal_obj = objv[2];

	tcl_ret = Tcl_GetWideIntFromObj(interp, signal_obj, &sig_wide);
	if (tcl_ret != TCL_OK) {
		switch (tuapi_internal_simplehash_obj(signal_obj)) {
			case 0x122ad0: /* HUP */
			case 0x98f364d0: /* SIGHUP */
				sig = SIGHUP;
				break;
			case 0x126754: /* INT */
			case 0x98f32954: /* SIGINT */
				sig = SIGINT;
				break;
			case 0xa3564d4: /* QUIT */
			case 0x7a9242d4: /* SIGQUIT */
				sig = SIGQUIT;
				break;
			case 0x12664c: /* ILL */
			case 0x98f3284c: /* SIGILL */
				sig = SIGILL;
				break;
			case 0xa94a0d0: /* TRAP */
			case 0x7a3386d0: /* SIGTRAP */
				sig = SIGTRAP;
				break;
			case 0x830a954: /* ABRT */
			case 0x78978f54: /* SIGABRT */
				sig = SIGABRT;
				break;
			case 0x1267d4: /* IOT */
			case 0x98f329d4: /* SIGIOT */
				sig = SIGIOT;
				break;
			case 0x10aad3: /* BUS */
			case 0x98f1e4d3: /* SIGBUS */
				sig = SIGBUS;
				break;
			case 0x11a845: /* FPE */
			case 0x98f0e645: /* SIGFPE */
				sig = SIGFPE;
				break;
			case 0x972664c: /* KILL */
			case 0x79d5404c: /* SIGKILL */
				sig = SIGKILL;
				break;
			case 0xab4e931: /* USR1 */
			case 0x7a13cf31: /* SIGUSR1 */
				sig = SIGUSR1;
				break;
			case 0xa7163d6: /* SEGV */
			case 0x7ad645d6: /* SIGSEGV */
				sig = SIGSEGV;
				break;
			case 0xab4e932: /* USR2 */
			case 0x7a13cf32: /* SIGUSR2 */
				sig = SIGUSR2;
				break;
			case 0xa126845: /* PIPE */
			case 0x7ab54e45: /* SIGPIPE */
				sig = SIGPIPE;
				break;
			case 0x833294d: /* ALRM */
			case 0x78940f4d: /* SIGALRM */
				sig = SIGALRM;
				break;
			case 0xa91694d: /* TERM */
			case 0x7a364f4d: /* SIGTERM */
				sig = SIGTERM;
				break;
			case 0x4970e8d4: /* STKFLT */
			case 0x80fefc54: /* SIGSTKFLT */
				sig = SIGSTKFLT;
				break;
			case 0x8722644: /* CHLD */
			case 0x78d50044: /* SIGCHLD */
				sig = SIGCHLD;
				break;
			case 0x873e754: /* CONT */
			case 0x78d4c154: /* SIGCONT */
				sig = SIGCONT;
				break;
			case 0xa7527d0: /* STOP */
			case 0x7ad201d0: /* SIGSTOP */
				sig = SIGSTOP;
				break;
			case 0xa94ea50: /* TSTP */
			case 0x7a33cc50: /* SIGTSTP */
				sig = SIGTSTP;
				break;
			case 0xa9524ce: /* TTIN */
			case 0x7a3202ce: /* SIGTTIN */
				sig = SIGTTIN;
				break;
			case 0xa9527d5: /* TTOU */
			case 0x7a3201d5: /* SIGTTOU */
				sig = SIGTTOU;
				break;
			case 0x156947: /* URG */
			case 0x98f42747: /* SIGURG */
				sig = SIGURG;
				break;
			case 0xb10e855: /* XCPU */
			case 0x7bb7ce55: /* SIGXCPU */
				sig = SIGXCPU;
				break;
			case 0xb11a9da: /* XFSZ */
			case 0x7bb68fda: /* SIGXFSZ */
				sig = SIGXFSZ;
				break;
			case 0x483273cd: /* VTALRM */
			case 0x81bc674d: /* SIGVTALRM */
				sig = SIGVTALRM;
				break;
			case 0xa14a7c6: /* PROF */
			case 0x7ab381c6: /* SIGPROF */
				sig = SIGPROF;
				break;
			case 0x7933a348: /* WINCH */
			case 0x2aa0bf48: /* SIGWINCH */
				sig = SIGWINCH;
				break;
			case 0x24cf: /* IO */
			case 0x3931e64f: /* SIGIO */
				sig = SIGIO;
				break;
			case 0x142bd2: /* PWR */
			case 0x98f565d2: /* SIGPWR */
				sig = SIGPWR;
				break;
			case 0x14ecd3: /* SYS */
			case 0x98f5a2d3: /* SIGSYS */
				sig = SIGSYS;
				break;
			default:
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown signal \"%s\"", Tcl_GetString(signal_obj)));

				return(TCL_ERROR);
		}
	} else {
		sig = sig_wide;
	}

	kill_ret = kill(pid, sig);
	if (kill_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_ps(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_execve(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char **argv = NULL;
	char *file;
	int idx;

	if (objc < 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::execve file ?args ...?\"", -1));

		return(TCL_ERROR);
	}

	/* Find executable */
	file = Tcl_GetString(objv[1]);

	/* Generate argument array */
	argv = malloc(sizeof(*argv) * (objc - 1));

	for (idx = 2; idx < objc; idx++) {
		argv[idx - 2] = Tcl_GetString(objv[idx]);
	}
	argv[objc - 2] = NULL;

	/* Pass execution to new file */
	execve(file, argv, environ);

	/* If the new image could not take over, something went wrong -- report error */
	Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

	return(TCL_ERROR);
}

static int tuapi_losetup(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	char *file, *loopdev;
	int chk_ret;
	int loopfd, filefd;

	if (objc != 3) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::losetup loopdev file\"", -1));

		return(TCL_ERROR);
	}

	loopdev = Tcl_GetString(objv[1]);
	file = Tcl_GetString(objv[2]);

	loopfd = open(loopdev, O_RDONLY);
	if (loopfd < 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	if (file[0] != '\0') {
		filefd = open(file, O_RDONLY);
		if (filefd < 0) {
			close(loopfd);

			Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

			return(TCL_ERROR);
		}

		chk_ret = ioctl(loopfd, LOOP_SET_FD, filefd);

		close(filefd);
	} else {
		chk_ret = ioctl(loopfd, LOOP_CLR_FD, 0);
	}

	close(loopfd);

	if (chk_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static void tuapi_private_append_sockaddr_to_tclobj(Tcl_Interp *interp, Tcl_Obj *list, char *header, struct sockaddr *addr) {
	char addr_buf[INET6_ADDRSTRLEN + INET_ADDRSTRLEN + 1], *chk_inp;

	switch (addr->sa_family) {
		case AF_INET: /* IPv4 */
		case AF_INET6: /* IPv6 */
			switch (addr->sa_family) {
				case AF_INET: /* IPv4 */
					chk_inp = (char *) inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, addr_buf, sizeof(addr_buf));
					break;
				case AF_INET6: /* IPv6 */
					chk_inp = (char *) inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, addr_buf, sizeof(addr_buf));
					break;
			}

			if (chk_inp == NULL) {
				break;
			}

			if (header) {
				Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(header, -1));
			}

			Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(addr_buf, -1));

			break;
	}

	return;
}

static int tuapi_private_get_sockaddr_from_obj(Tcl_Obj *value, void *target) {
	struct sockaddr_in local_v4;
	struct sockaddr_in6 local_v6;
	const char *addr_str;
	int inetpton_ret;

	addr_str = Tcl_GetString(value);

	memset(&local_v4, 0, sizeof(local_v4));
	inetpton_ret = inet_pton(AF_INET, addr_str, &local_v4.sin_addr);
	if (inetpton_ret == 1) {
		local_v4.sin_family = AF_INET;

		memcpy(target, &local_v4, sizeof(local_v4));

		return(0);
	}

	memset(&local_v6, 0, sizeof(local_v6));
	inetpton_ret = inet_pton(AF_INET6, addr_str, &local_v6.sin6_addr);
	if (inetpton_ret == 1) {
		local_v6.sin6_family = AF_INET6;

		memcpy(target, &local_v6, sizeof(local_v6));

		return(0);
	}

	return(-1);
}

static int tuapi_ifconfig_list(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock) {
	Tcl_Obj *tcl_iface_list;
	struct ifconf ifaces_cfg;
	struct ifreq *iface_req = NULL;
	int iface_req_cnt = 224, iface_req_len;
	int idx, iface_cnt;
	int ioctl_ret, tcl_ret;

	iface_req_len = iface_req_cnt * sizeof(*iface_req);
	iface_req = malloc(iface_req_len);
	if (iface_req == NULL) {
		/* Report failure */
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to allocate memory", -1));

		return(TCL_ERROR);
	}

	ifaces_cfg.ifc_req = iface_req;
	ifaces_cfg.ifc_len = iface_req_len;
	ioctl_ret = ioctl(sock, SIOCGIFCONF, &ifaces_cfg);
	if (ioctl_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("ioctl failed", -1));

		free(iface_req);

		return(TCL_ERROR);
	}

	iface_cnt = ifaces_cfg.ifc_len / sizeof(*iface_req);

	tcl_iface_list = Tcl_NewObj();

	for (idx = 0; idx < iface_cnt; idx++) {
		tcl_ret = Tcl_ListObjAppendElement(interp, tcl_iface_list, Tcl_NewStringObj(iface_req[idx].ifr_name, -1));
		if (tcl_ret != TCL_OK) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to append to list", -1));

			free(iface_req);

			return(TCL_ERROR);
		}
	}

	free(iface_req);

	Tcl_SetObjResult(interp, tcl_iface_list);

	return(TCL_OK);
}

static int tuapi_ifconfig_info(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock, int sock_v4, int sock_v6) {
	Tcl_Obj *retlist, *flags;
	struct ifreq iface_req;
	unsigned char *addr_data;
	const char *link_encap;
	const char *iface;
	int flags_bitmask, flag_broadcast = 0, flag_pointopoint = 0;
	int ioctl_ret;

	retlist = Tcl_NewObj();

	iface = Tcl_GetString(objv[1]);

	if ((strlen(iface) + 1) >= IFNAMSIZ) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("interface name too long", -1));

		return(TCL_ERROR);
	}

	strcpy(iface_req.ifr_name, iface);

	/*
	 * All interfaces should have flags, so use it as a check for interface
	 * existance
	 */
	ioctl_ret = ioctl(sock, SIOCGIFFLAGS, &iface_req);
	if (ioctl_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("invalid interface", -1));

		return(TCL_ERROR);
	}

	/* Create list of flags */
	flags = Tcl_NewObj();
	flags_bitmask = iface_req.ifr_flags;

	if ((flags_bitmask & IFF_UP) == IFF_UP) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("UP", -1));
	}
	if ((flags_bitmask & IFF_BROADCAST) == IFF_BROADCAST) {
		flag_broadcast = 1;

		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("BROADCAST", -1));
	}
	if ((flags_bitmask & IFF_POINTOPOINT) == IFF_POINTOPOINT) {
		flag_pointopoint = 1;

		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("POINTOPOINT", -1));
	}
	if ((flags_bitmask & IFF_DEBUG) == IFF_DEBUG) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("DEBUG", -1));
	}
	if ((flags_bitmask & IFF_LOOPBACK) == IFF_LOOPBACK) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("LOOPBACK", -1));
	}
	if ((flags_bitmask & IFF_NOTRAILERS) == IFF_NOTRAILERS) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("NOTRAILERS", -1));
	}
	if ((flags_bitmask & IFF_RUNNING) == IFF_RUNNING) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("RUNNING", -1));
	}
	if ((flags_bitmask & IFF_NOARP) == IFF_NOARP) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("NOARP", -1));
	}
	if ((flags_bitmask & IFF_PROMISC) == IFF_PROMISC) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("PROMISC", -1));
	}
	if ((flags_bitmask & IFF_ALLMULTI) == IFF_ALLMULTI) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("ALLMULTI", -1));
	}
	if ((flags_bitmask & IFF_MASTER) == IFF_MASTER) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("MASTER", -1));
	}
	if ((flags_bitmask & IFF_SLAVE) == IFF_SLAVE) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("SLAVE", -1));
	}
	if ((flags_bitmask & IFF_MULTICAST) == IFF_MULTICAST) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("MULTICAST", -1));
	}
	if ((flags_bitmask & IFF_PORTSEL) == IFF_PORTSEL) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("PORTSEL", -1));
	}
	if ((flags_bitmask & IFF_AUTOMEDIA) == IFF_AUTOMEDIA) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("AUTOMEDIA", -1));
	}
	if ((flags_bitmask & IFF_DYNAMIC) == IFF_DYNAMIC) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("DYNAMIC", -1));
	}
	if ((flags_bitmask & IFF_LOWER_UP) == IFF_LOWER_UP) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("LOWER_UP", -1));
	}
	if ((flags_bitmask & IFF_DORMANT) == IFF_DORMANT) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("DORMANT", -1));
	}
#ifdef IFF_ECHO
	if ((flags_bitmask & IFF_ECHO) == IFF_ECHO) {
		Tcl_ListObjAppendElement(interp, flags, Tcl_NewStringObj("ECHO", -1));
	}
#endif

	/* Add array-compliant/dict entry to the return list */
	Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("flags", -1));
	Tcl_ListObjAppendElement(interp, retlist, flags);

	/* Fetch other attributes from the interface */
	ioctl_ret = ioctl(sock, SIOCGIFHWADDR, &iface_req);
	if (ioctl_ret == 0) {
		link_encap = "unknown";

		addr_data = (unsigned char *) iface_req.ifr_hwaddr.sa_data;
		switch (iface_req.ifr_hwaddr.sa_family) {
			case ARPHRD_ETHER:
				link_encap = "ethernet";

				Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("hwaddr", -1));
				Tcl_ListObjAppendElement(interp, retlist,
				  Tcl_ObjPrintf("%02x:%02x:%02x:%02x:%02x:%02x",
				    addr_data[0],
				    addr_data[1],
				    addr_data[2],
				    addr_data[3],
				    addr_data[4],
				    addr_data[5]
				  )
				);

				break;
			case ARPHRD_AX25:
				link_encap = "ax25";
				break;
			case ARPHRD_PRONET:
				link_encap = "pronet";
				break;
			case ARPHRD_CHAOS:
				link_encap = "chaos";
				break;
			case ARPHRD_IEEE802:
				link_encap = "ieee802";
				break;
			case ARPHRD_ARCNET:
				link_encap = "arcnet";
				break;
			case ARPHRD_APPLETLK:
				link_encap = "appletlk";
				break;
			case ARPHRD_DLCI:
				link_encap = "dlci";
				break;
			case ARPHRD_ATM:
				link_encap = "atm";
				break;
			case ARPHRD_METRICOM:
				link_encap = "metricom";
				break;
			case ARPHRD_IEEE1394:
				link_encap = "ieee1394";
				break;
			case ARPHRD_EUI64:
				link_encap = "eui64";
				break;
			case ARPHRD_INFINIBAND:
				link_encap = "infiniband";
				break;
			case ARPHRD_SLIP:
				link_encap = "slip";
				break;
			case ARPHRD_CSLIP:
				link_encap = "cslip";
				break;
			case ARPHRD_SLIP6:
				link_encap = "slip6";
				break;
			case ARPHRD_CSLIP6:
				link_encap = "cslip6";
				break;
			case ARPHRD_RSRVD:
				link_encap = "rsrvd";
				break;
			case ARPHRD_ADAPT:
				link_encap = "adapt";
				break;
			case ARPHRD_ROSE:
				link_encap = "rose";
				break;
			case ARPHRD_X25:
				link_encap = "x25";
				break;
			case ARPHRD_HWX25:
				link_encap = "hwx25";
				break;
			case ARPHRD_CAN:
				link_encap = "can";
				break;
			case ARPHRD_PPP:
				link_encap = "ppp";
				break;
			case ARPHRD_CISCO:
				link_encap = "cisco";
				break;
			case ARPHRD_LAPB:
				link_encap = "lapb";
				break;
			case ARPHRD_DDCMP:
				link_encap = "ddcmp";
				break;
			case ARPHRD_RAWHDLC:
				link_encap = "rawhdlc";
				break;
			case ARPHRD_TUNNEL:
				link_encap = "tunnel";
				break;
			case ARPHRD_TUNNEL6:
				link_encap = "tunnel6";
				break;
			case ARPHRD_FRAD:
				link_encap = "frad";
				break;
			case ARPHRD_SKIP:
				link_encap = "skip";
				break;
			case ARPHRD_LOOPBACK:
				link_encap = "loopback";
				break;
			case ARPHRD_LOCALTLK:
				link_encap = "localtalk";
				break;
			case ARPHRD_FDDI:
				link_encap = "fddi";
				break;
			case ARPHRD_BIF:
				link_encap = "bif";
				break;
			case ARPHRD_SIT:
				link_encap = "sit";
				break;
			case ARPHRD_IPDDP:
				link_encap = "ipddp";
				break;
			case ARPHRD_IPGRE:
				link_encap = "gre";
				break;
			case ARPHRD_PIMREG:
				link_encap = "pimreg";
				break;
			case ARPHRD_HIPPI:
				link_encap = "hippi";
				break;
			case ARPHRD_ASH:
				link_encap = "ash";
				break;
			case ARPHRD_ECONET:
				link_encap = "econet";
				break;
			case ARPHRD_IRDA:
				link_encap = "irda";
				break;
		}

		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("link_encap", -1));
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj(link_encap, -1));
	}

	ioctl_ret = ioctl(sock, SIOCGIFMETRIC, &iface_req);
	if (ioctl_ret == 0) {
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("metric", -1));
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewWideIntObj(iface_req.ifr_metric + 1));
	}

	ioctl_ret = ioctl(sock, SIOCGIFMTU, &iface_req);
	if (ioctl_ret == 0) {
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("mtu", -1));
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewWideIntObj(iface_req.ifr_mtu));
	}

	ioctl_ret = ioctl(sock, SIOCGIFINDEX, &iface_req);
	if (ioctl_ret == 0) {
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewStringObj("index", -1));
		Tcl_ListObjAppendElement(interp, retlist, Tcl_NewWideIntObj(iface_req.ifr_ifindex));
	}

	if (sock_v4 != -1) {
		ioctl_ret = ioctl(sock_v4, SIOCGIFADDR, &iface_req);
		if (ioctl_ret == 0) {
			tuapi_private_append_sockaddr_to_tclobj(interp, retlist, "address", &iface_req.ifr_addr);
		}

		if (flag_pointopoint) {
			/* Point-to-Point interfaces */
			ioctl_ret = ioctl(sock_v4, SIOCGIFDSTADDR, &iface_req);
			if (ioctl_ret == 0) {
				tuapi_private_append_sockaddr_to_tclobj(interp, retlist, "destination", &iface_req.ifr_addr);
			}
		}

		if (flag_broadcast) {
			/* Broadcast interfaces */
			ioctl_ret = ioctl(sock_v4, SIOCGIFBRDADDR, &iface_req);
			if (ioctl_ret == 0) {
				tuapi_private_append_sockaddr_to_tclobj(interp, retlist, "broadcast", &iface_req.ifr_addr);
			}
		}

		ioctl_ret = ioctl(sock_v4, SIOCGIFNETMASK, &iface_req);
		if (ioctl_ret == 0) {
			tuapi_private_append_sockaddr_to_tclobj(interp, retlist, "netmask", &iface_req.ifr_addr);
		}
	}

	Tcl_SetObjResult(interp, retlist);

	return(TCL_OK);
}

static int tuapi_ifconfig_conf(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock, int sock_v4, int sock_v6) {
	Tcl_Obj *option_name_obj, *option_val_obj;
	Tcl_Obj **flags_objv;
	struct ifreq iface_req;
	struct sockaddr *tmp_ioctl_addr;
	const char *iface;
	short flags;
	int flags_objc;
	int tmp_sock, tmp_ioctl;
	int ioctl_ret, tcl_ret, parse_ret;

	iface = Tcl_GetString(objv[1]);

	if ((strlen(iface) + 1) >= IFNAMSIZ) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("interface name too long", -1));

		return(TCL_ERROR);
	}

	objc -= 2;
	objv += 2;

	for (; objc > 0; objc--,objv++) {
		/* Prepare for an ioctl() */
		strcpy(iface_req.ifr_name, iface);
		tmp_ioctl = -1;

		option_name_obj = objv[0];

		if (objc == 1) {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("option \"%s\" requires an argument", Tcl_GetString(option_name_obj)));

			return(TCL_ERROR);
		}

		objc--;
		objv++;

		option_val_obj = objv[0];

		switch (tuapi_internal_simplehash_obj(option_name_obj)) {
			case 0x6d9870f3: /* flags */
				flags = 0;

				tcl_ret = Tcl_ListObjGetElements(interp, option_val_obj, &flags_objc, &flags_objv);
				if (tcl_ret != TCL_OK) {
					return(tcl_ret);
				}

				for (; flags_objc > 0; flags_objc--,flags_objv++) {
					switch (tuapi_internal_simplehash_obj(flags_objv[0])) {
						case 0x2ad0: /* UP */
							flags |= IFF_UP;
							break;
						case 0x1aef7f54: /* BROADCAST */
							flags |= IFF_BROADCAST;
							break;
						case 0xc252abd4: /* POINTOPOINT */
							flags |= IFF_POINTOPOINT;
							break;
						case 0x48b0a8c7: /* DEBUG */
							flags |= IFF_DEBUG;
							break;
						case 0x4d3dbcd3: /* NOTRAILERS */
							flags |= IFF_NOTRAILERS;
							break;
						case 0xe9773147: /* RUNNING */
							flags |= IFF_RUNNING;
							break;
						case 0xe9f06b50: /* NOARP */
							flags |= IFF_NOARP;
							break;
						case 0xf91323c3: /* PROMISC */
							flags |= IFF_PROMISC;
							break;
						case 0x9b2a1849: /* ALLMULTI */
							flags |= IFF_ALLMULTI;
							break;
						case 0x1a7414d2: /* MASTER */
							flags |= IFF_MASTER;
							break;
						case 0x399069c5: /* SLAVE */
							flags |= IFF_SLAVE;
							break;
						case 0x4de928d4: /* MULTICAST */
							flags |= IFF_MULTICAST;
							break;
						case 0x2a35dc4c: /* PORTSEL */
							flags |= IFF_PORTSEL;
							break;
						case 0xd180ac1: /* AUTOMEDIA */
							flags |= IFF_AUTOMEDIA;
							break;
						case 0xe8ba02c3: /* DYNAMIC */
							flags |= IFF_DYNAMIC;
							break;
						case 0x16c8b4d0: /* LOWER_UP */
							flags |= IFF_LOWER_UP;
							break;
						case 0x293959d4: /* DORMANT */
							flags |= IFF_DORMANT;
							break;
#ifdef IFF_ECHO
						case 0x8b0e44f: /* ECHO */
							flags |= IFF_ECHO;
							break;
#endif
					}
				}

				iface_req.ifr_flags = flags;

				ioctl_ret = ioctl(sock, SIOCSIFFLAGS, &iface_req);
				if (ioctl_ret != 0) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

					return(TCL_ERROR);
				}

				break;
			case 0x5e9d03e3: /* metric */
			case 0x1b7a75: /* mtu */
			case 0x7c3891f2: /* hwaddr */
			case 0xbf72a969: /* addmulti */
			case 0xba708969: /* delmulti */
			case 0xdd876e5: /* name */
					Tcl_SetObjResult(interp, Tcl_ObjPrintf("option \"%s\" unsupported", Tcl_GetString(option_name_obj)));

					return(TCL_ERROR);
				break;
			case 0x4e9aeaf3: /* address */
				if (tmp_ioctl == -1) {
					tmp_ioctl = SIOCSIFADDR;
					tmp_ioctl_addr = &iface_req.ifr_addr;
				}

			case 0xec05706e: /* destination */
				if (tmp_ioctl == -1) {
					tmp_ioctl = SIOCSIFDSTADDR;
					tmp_ioctl_addr = &iface_req.ifr_dstaddr;
				}

			case 0x3ea7e674: /* broadcast */
				if (tmp_ioctl == -1) {
					tmp_ioctl = SIOCSIFBRDADDR;
					tmp_ioctl_addr = &iface_req.ifr_broadaddr;
				}

			case 0x4d65ee6b: /* netmask */
				if (tmp_ioctl == -1) {
					tmp_ioctl = SIOCSIFNETMASK;
					tmp_ioctl_addr = &iface_req.ifr_netmask;
				}

				parse_ret = tuapi_private_get_sockaddr_from_obj(option_val_obj, tmp_ioctl_addr);
				if (parse_ret != 0) {
					Tcl_SetObjResult(interp, Tcl_ObjPrintf("unable to parse \"%s\" as an address", Tcl_GetString(option_val_obj)));

					return(TCL_ERROR);
				}

				switch (tmp_ioctl_addr->sa_family) {
					case AF_INET:
						tmp_sock = sock_v4;

						break;
					case AF_INET6:
						tmp_sock = sock_v6;

						break;
					default:
						Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to determine address family of sockaddr", -1));
						return(TCL_ERROR);
				}

				ioctl_ret = ioctl(tmp_sock, tmp_ioctl, &iface_req);
				if (ioctl_ret != 0) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

					return(TCL_ERROR);
				}

				break;
			default:
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("unknown option \"%s\"", Tcl_GetString(option_name_obj)));

				return(TCL_ERROR);
		}
	}

	return(TCL_OK);
}

static int tuapi_ifconfig(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	int sock_v4, sock_v6, sock;
	int retval = TCL_ERROR;

	sock = tuapi_internal_getsock(&sock_v4, &sock_v6);
	if (sock == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to create socket", -1));

		return(TCL_ERROR);
	}

	switch (objc) {
		case 0:
		case 1: /* No arguments, list all interfaces */
			retval = tuapi_ifconfig_list(cd, interp, objc, objv, sock);

			break;
		case 2: /* One argument, give information about the interface */
			retval = tuapi_ifconfig_info(cd, interp, objc, objv, sock, sock_v4, sock_v6);

			break;
		default:
			/* Otherwise, configure the interace */
			retval = tuapi_ifconfig_conf(cd, interp, objc, objv, sock, sock_v4, sock_v6);

			break;
	}

	/* Cleanup */
	if (sock_v4 != -1) {
		close(sock_v4);
	}

	if (sock_v6 != -1) {
		close(sock_v6);
	}

	return(retval);
}

static int tuapi_route_list(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock_v4, int sock_v6) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_route_conf(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock_v4, int sock_v6) {
	Tcl_WideInt option_val_wide; 
	Tcl_Obj *operation_obj, *dest_obj, *destmask_obj;
	Tcl_Obj *option_name_obj, *option_val_obj;
	struct rtentry route;
	int sock;
	int ioctl_id;
	int tcl_ret, ioctl_ret, parse_ret;

	if (objc < 4) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::route operation destination destination_mask ?options?\"", -1));

		return(TCL_ERROR);
	}

	/* Clear object values */
	memset(&route, 0, sizeof(route));

	/* Determine operation */
	operation_obj = objv[1];
	switch (tuapi_internal_simplehash_obj(operation_obj)) {
		case 0x187264: /* add */
			ioctl_id = SIOCADDRT;
			break;
		case 0x1932ec: /* del */
		case 0x5d98e965: /* delete */
			ioctl_id = SIOCDELRT;
			break;
		default:
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be add, or delete", Tcl_GetString(operation_obj)));

			return(TCL_ERROR);
	}

	/* Set default flags */
	route.rt_flags = RTF_UP;

	/* Parse destination address */
	dest_obj = objv[2];
	parse_ret = tuapi_private_get_sockaddr_from_obj(dest_obj, &route.rt_dst);
	if (parse_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("unable to parse \"%s\" as an address", Tcl_GetString(dest_obj)));

		return(TCL_ERROR);
	}

	/* Parse destination netmask */
	destmask_obj = objv[3];
	parse_ret = tuapi_private_get_sockaddr_from_obj(destmask_obj, &route.rt_genmask);
	if (parse_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf("unable to parse \"%s\" as an address", Tcl_GetString(destmask_obj)));

		return(TCL_ERROR);
	}

	if (route.rt_dst.sa_family != route.rt_genmask.sa_family) {
		Tcl_SetObjResult(interp,
		  Tcl_ObjPrintf("destination (\"%s\") and destination_mask (\"%s\") are different classes",
		    Tcl_GetString(dest_obj),
		    Tcl_GetString(destmask_obj)
		  )
		);

		return(TCL_ERROR);
	}

	switch (route.rt_dst.sa_family) {
		case AF_INET: /* IPv4 */
			if (sock_v4 == -1) {
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("address \"%s\" is IPv4, but unable to create IPv4 socket", Tcl_GetString(dest_obj)));

				return(TCL_ERROR);
			}

			if (((struct sockaddr_in *) &route.rt_genmask)->sin_addr.s_addr == INADDR_BROADCAST) {
				route.rt_flags |= RTF_HOST;
			}

			sock = sock_v4;

			break;
		case AF_INET6: /* IPv6 */
			if (sock_v6 == -1) {
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("address \"%s\" is IPv6, but unable to create IPv6 socket", Tcl_GetString(dest_obj)));

				return(TCL_ERROR);
			}

			sock = sock_v6;

			break;
		default:
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("unable to determine type of address for \"%s\"", Tcl_GetString(dest_obj)));

			return(TCL_ERROR);
	}

	/* Parse remaining options */
	objc -= 4;
	objv += 4;

	for (; objc > 0; objc--,objv++) {
		option_name_obj = objv[0];

		if (objc < 2) {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf("option \"%s\" requires an argument", Tcl_GetString(option_name_obj)));

			return(TCL_ERROR);
		}

		objc--;
		objv++;

		option_val_obj = objv[0];

		switch (tuapi_internal_simplehash_obj(option_name_obj)) {
			case 0x4c727779: /* gateway */
				parse_ret = tuapi_private_get_sockaddr_from_obj(option_val_obj, &route.rt_gateway);
				if (parse_ret != 0) {
					Tcl_SetObjResult(interp, Tcl_ObjPrintf("unable to parse \"%s\" as an address", Tcl_GetString(option_val_obj)));

					return(TCL_ERROR);
				}

				route.rt_flags &= (~RTF_HOST);
				route.rt_flags |= RTF_GATEWAY;

				break;
			case 0x1b7a75: /* mtu */
				tcl_ret = Tcl_GetWideIntFromObj(interp, option_val_obj, &option_val_wide);
				if (tcl_ret != TCL_OK) {
					return(tcl_ret);
				}

				route.rt_flags |= RTF_MTU;
				route.rt_mtu = option_val_wide;

				break;
			case 0x5e9d03e3: /* metric */
				tcl_ret = Tcl_GetWideIntFromObj(interp, option_val_obj, &option_val_wide);
				if (tcl_ret != TCL_OK) {
					return(tcl_ret);
				}

				route.rt_metric = option_val_wide;

				break;
			case 0x9dd8e8f7: /* window */
				tcl_ret = Tcl_GetWideIntFromObj(interp, option_val_obj, &option_val_wide);
				if (tcl_ret != TCL_OK) {
					return(tcl_ret);
				}

				route.rt_flags |= RTF_WINDOW;
				route.rt_window = option_val_wide;

				break;
			case 0x1932f6: /* dev */
			case 0x5edbe2e5: /* device */
				route.rt_dev = strdup(Tcl_GetString(option_val_obj));

				break;
			default:
				Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be gateway, mtu, metric, device, or window", Tcl_GetString(option_name_obj)));

				return(TCL_ERROR);
		}
	}

	/* Request route change */
	ioctl_ret = ioctl(sock, ioctl_id, &route);
	if (ioctl_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_route(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	int sock_v4, sock_v6, sock;
	int retval = TCL_ERROR;

	sock = tuapi_internal_getsock(&sock_v4, &sock_v6);
	if (sock == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to create socket", -1));

		return(TCL_ERROR);
	}

	switch (objc) {
		case 0:
		case 1: /* No arguments, list all interfaces */
			retval = tuapi_route_list(cd, interp, objc, objv, sock_v4, sock_v6);

			break;
		default:
			/* Otherwise, modify routes */
			retval = tuapi_route_conf(cd, interp, objc, objv, sock_v4, sock_v6);

			break;
	}

	/* Cleanup */
	if (sock_v4 != -1) {
		close(sock_v4);
	}

	if (sock_v6 != -1) {
		close(sock_v6);
	}

	return(retval);
}

static int tuapi_brctl_list(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	return(TCL_ERROR);
}

static int tuapi_brctl_conf(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int sock) {
	Tcl_Obj *operation_obj, *bridge_name_obj, *interface_name_obj;
	unsigned long arg[4];
	struct ifreq ifr;
	int ioctl_ret, ioctl_id;
	int add = 0;

	/* Determine operation */
	operation_obj = objv[1];
	switch (tuapi_internal_simplehash_obj(operation_obj)) {
		case 0x1c993272: /* addbr */
			add = 1;
		case 0x4cbb3272: /* delbr */
			if (objc != 3) {
				if (add) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::brctl addbr bridge\"", -1));
				} else {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::brctl delbr bridge\"", -1));
				}

				return(TCL_ERROR);
			}

			bridge_name_obj = objv[2];

			if (add) {
				arg[0] = BRCTL_ADD_BRIDGE;
			} else {
				arg[0] = BRCTL_DEL_BRIDGE;
			}

			arg[1] = (unsigned long) Tcl_GetString(bridge_name_obj);
			arg[2] = 0;

			ioctl_ret = ioctl(sock, SIOCGIFBR, &arg); 

			break;
		case 0x1C9937E6: /* addif */
			add = 1;
		case 0x4cbb37e6: /* delif */
			if (objc != 4) {
				if (add) {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::brctl addif bridge interface\"", -1));
				} else {
					Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::brctl delif bridge interface\"", -1));
				}

				return(TCL_ERROR);
			}

			if (add) {
				ioctl_id = SIOCBRADDIF;
			} else {
				ioctl_id = SIOCBRDELIF;
			}

			bridge_name_obj = objv[2];
			interface_name_obj = objv[3];

			memset(&ifr, 0, sizeof(ifr));
			snprintf(ifr.ifr_name, IFNAMSIZ, "%s", Tcl_GetString(interface_name_obj));

			ioctl_ret = ioctl(sock, SIOCGIFINDEX, (void *) &ifr);
			if (ioctl_ret == 0) {
				snprintf(ifr.ifr_name, IFNAMSIZ, "%s", Tcl_GetString(bridge_name_obj));
				ioctl_ret = ioctl(sock, ioctl_id, (void *) &ifr);
			}

			break;
	}

	if (ioctl_ret < 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	return(TCL_OK);
}

static int tuapi_brctl(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	int sock_v4, sock_v6, sock;
	int retval = TCL_ERROR;

	sock = tuapi_internal_getsock(&sock_v4, &sock_v6);
	if (sock == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to create socket", -1));

		return(TCL_ERROR);
	}

	switch (objc) {
		case 0:
		case 1: /* No arguments, list all bridges */
			retval = tuapi_brctl_list(cd, interp, objc, objv, sock);

			break;
		default:
			/* Otherwise, modify routes */
			retval = tuapi_brctl_conf(cd, interp, objc, objv, sock);

			break;
	}

	/* Cleanup */
	if (sock_v4 != -1) {
		close(sock_v4);
	}

	if (sock_v6 != -1) {
		close(sock_v6);
	}

	return(retval);
}

static int tuapi_vconfig(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	int sock_v4, sock_v6, sock;
	int retval = TCL_ERROR;

	sock = tuapi_internal_getsock(&sock_v4, &sock_v6);
	if (sock == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to create socket", -1));

		return(TCL_ERROR);
	}

	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));

	/* Cleanup */
	if (sock_v4 != -1) {
		close(sock_v4);
	}

	if (sock_v6 != -1) {
		close(sock_v6);
	}

	return(retval);
}

#ifndef DISABLE_UNIX_SOCKETS
struct tuapi_socket_unix__chan_id {
	int fd;
	Tcl_Channel chan;
};

static int tuapi_socket_unix__chan_close(ClientData id_p, Tcl_Interp *interp) {
	struct tuapi_socket_unix__chan_id *id;
	int fd;

	id = id_p;

	fd = id->fd;

	close(fd);

	free(id);

	return(0);
}

static int tuapi_socket_unix__chan_read(ClientData id_p, char *buf, int bufsize, int *errorCodePtr) {
	struct tuapi_socket_unix__chan_id *id;
	ssize_t read_ret;
	int fd;
	int retval;

	id = id_p;

	fd = id->fd;

	read_ret = read(fd, buf, bufsize);
	if (read_ret < 0) {
		*errorCodePtr = errno;

		return(-1);
	}

	retval = read_ret;

	return(retval);
}

static int tuapi_socket_unix__chan_write(ClientData id_p, const char *buf, int toWrite, int *errorCodePtr) {
	struct tuapi_socket_unix__chan_id *id;
	ssize_t write_ret;
	int fd;
	int bytesWritten;

	id = id_p;

	fd = id->fd;

	bytesWritten = 0;
	while (toWrite) {
		write_ret = write(fd, buf, toWrite);
		if (write_ret == 0) {
			break;
		}

		if (write_ret < 0) {
			*errorCodePtr = errno;

			return(-1);
		}

		toWrite -= write_ret;
		buf += write_ret;
		bytesWritten += write_ret;
	}

	if (bytesWritten == 0) {
		*errorCodePtr = EAGAIN;

		return(-1);
	}

	return(bytesWritten);
}

static void tuapi_socket_unix__chan_eventhandler(ClientData id_p, int mask) {
	struct tuapi_socket_unix__chan_id *id;
	Tcl_Channel chan;

	id = id_p;

	chan = id->chan;

	if (!chan) {
		return;
	}

	Tcl_NotifyChannel(chan, mask);
}

static void tuapi_socket_unix__chan_watch(ClientData id_p, int mask) {
	struct tuapi_socket_unix__chan_id *id;
	int fd;

	id = id_p;

	fd = id->fd;

	Tcl_CreateFileHandler(fd, mask, tuapi_socket_unix__chan_eventhandler, id);

	return;
}

static int tuapi_socket_unix__chan_gethandle(ClientData id_p, int direction, ClientData *handlePtr) {
	struct tuapi_socket_unix__chan_id *id;
	int fd;
	ClientData fd_cd;

	id = id_p;

	fd = id->fd;

	memcpy(&fd_cd, &fd, sizeof(fd));

	*handlePtr = fd_cd;

	return(TCL_OK);
}

static Tcl_Channel tuapi_socket_unix_sock2tclchan(int sock) {
	struct tuapi_socket_unix__chan_id *id;
	static Tcl_ChannelType tcl_chan_type;
	static int tcl_chan_type_init = 0;
	Tcl_Channel tcl_chan;
	char chan_name[32];
	int sock_flags;

	if (!tcl_chan_type_init) {
		tcl_chan_type.typeName = "socket";
		tcl_chan_type.version = TCL_CHANNEL_VERSION_2;
		tcl_chan_type.closeProc = tuapi_socket_unix__chan_close;
		tcl_chan_type.inputProc = tuapi_socket_unix__chan_read;
		tcl_chan_type.outputProc = tuapi_socket_unix__chan_write;
		tcl_chan_type.watchProc = tuapi_socket_unix__chan_watch;
		tcl_chan_type.getHandleProc = tuapi_socket_unix__chan_gethandle;
		tcl_chan_type.seekProc = NULL;
		tcl_chan_type.setOptionProc = NULL;
		tcl_chan_type.getOptionProc = NULL;
		tcl_chan_type.close2Proc = NULL;
		tcl_chan_type.blockModeProc = NULL;
		tcl_chan_type.flushProc = NULL;
		tcl_chan_type.handlerProc = NULL;
		tcl_chan_type.wideSeekProc = NULL;
		tcl_chan_type.threadActionProc = NULL;
		tcl_chan_type.truncateProc = NULL;

		tcl_chan_type_init = 1;
	}

	snprintf(chan_name, sizeof(chan_name), "sock%u", sock);

	id = malloc(sizeof(*id));
	if (id == NULL) {
		return(NULL);
	}

	id->fd = sock;
	id->chan = NULL;

	/* Configure socket as non-blocking */
	sock_flags = fcntl(sock, F_GETFL, 0);
	if (sock_flags == -1) {
		sock_flags = O_NONBLOCK;
	} else {
		sock_flags |= O_NONBLOCK;
	}
	fcntl(sock, F_SETFL, (long) sock_flags);

	/* Create the channel */
	tcl_chan = Tcl_CreateChannel(&tcl_chan_type, chan_name, id, TCL_READABLE | TCL_WRITABLE);

	/* Update the structure passed to each function to include the channel name */
	id->chan = tcl_chan;

	return(tcl_chan);
}

struct tuapi_socket_unix__chan_accept_cd {
	int fd;
	Tcl_Interp *interp;
	Tcl_Obj *command;
};

static void tuapi_socket_unix__chan_accept(ClientData cd_p, int mask) {
	struct tuapi_socket_unix__chan_accept_cd *cd;
	Tcl_Interp *interp;
	Tcl_Channel chan;
	Tcl_Obj *command, *command_to_run_objs[5], *command_to_run;
	int setsockopt_ret;
	int pass_creds_true = 1;
	int fd;
	int sock;

	if ((mask & TCL_READABLE) != TCL_READABLE) {
		return;
	}
	
	cd = cd_p;

	fd = cd->fd;
	interp = cd->interp;
	command = cd->command;

	sock = accept(fd, NULL, NULL);
	if (sock < 0) {
		return;
	}

	chan = tuapi_socket_unix_sock2tclchan(sock);
	if (chan == NULL) {
		close(sock);

		return;
	}

	setsockopt_ret = setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &pass_creds_true, sizeof(pass_creds_true));
	if (setsockopt_ret != 0) {
		close(sock);

		return;
	}

	Tcl_RegisterChannel(interp, chan);

	command_to_run_objs[0] = command;
	command_to_run_objs[1] = Tcl_NewStringObj(Tcl_GetChannelName(chan), -1);
	command_to_run_objs[2] = Tcl_NewStringObj("...uid...", -1); /* XXX: TODO */
	command_to_run_objs[3] = Tcl_NewStringObj("...gid...", -1); /* XXX: TODO */
	command_to_run_objs[4] = Tcl_NewStringObj("...pid...", -1); /* XXX: TODO */
	command_to_run = Tcl_ConcatObj(sizeof(command_to_run_objs) / sizeof(command_to_run_objs[0]), command_to_run_objs);

	Tcl_EvalObjEx(interp, command_to_run, TCL_EVAL_GLOBAL);

	return;
}

static int tuapi_socket_unix_server(ClientData cd, Tcl_Interp *interp, int sock, const char *path, Tcl_Obj *command) {
	struct tuapi_socket_unix__chan_accept_cd *accept_cd;
	struct sockaddr_un dest;
	ssize_t pathlen;
	int bind_ret, listen_ret;

	pathlen = strlen(path) + 1;
	if (pathlen <= 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("path too short", -1));

		return(TCL_ERROR);
	}

	if (pathlen > sizeof(dest.sun_path)) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("path too long", -1));

		return(TCL_ERROR);
	}

	dest.sun_family = AF_UNIX;
	memcpy(dest.sun_path, path, pathlen);

	bind_ret = bind(sock, (struct sockaddr *) &dest, sizeof(dest));
	if (bind_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	listen_ret = listen(sock, 2);
	if (listen_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	accept_cd = malloc(sizeof(*accept_cd));
	if (accept_cd == NULL) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	accept_cd->fd = sock;
	accept_cd->interp = interp;
	accept_cd->command = command;

	Tcl_IncrRefCount(command);

	Tcl_CreateFileHandler(sock, TCL_READABLE, tuapi_socket_unix__chan_accept, accept_cd);

	return(TCL_OK);
}

static int tuapi_socket_unix_client(ClientData cd, Tcl_Interp *interp, int sock, const char *path) {
	Tcl_Channel chan;
	struct sockaddr_un dest;
	ssize_t pathlen;
	int connect_ret, setsockopt_ret;
	int pass_creds_true = 1;

	pathlen = strlen(path) + 1;
	if (pathlen <= 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("path too short", -1));

		return(TCL_ERROR);
	}

	if (pathlen > sizeof(dest.sun_path)) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("path too long", -1));

		return(TCL_ERROR);
	}

	dest.sun_family = AF_UNIX;
	memcpy(dest.sun_path, path, pathlen);

	connect_ret = connect(sock, (struct sockaddr *) &dest, sizeof(dest));
	if (connect_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	setsockopt_ret = setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &pass_creds_true, sizeof(pass_creds_true));
	if (setsockopt_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	chan = tuapi_socket_unix_sock2tclchan(sock);
	if (chan == NULL) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("unable to create Tcl channel", -1));

		return(TCL_ERROR);
	}

	Tcl_RegisterChannel(interp, chan);

	Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1));

	return(TCL_OK);
}

static int tuapi_socket_unix(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_Obj *path_obj, *command_obj;
	char *path;
	int retval;
	int sock;

	if (objc < 2) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::socket_unix path\" or \"::tuapi::syscall::socket_unix -server command path\"", -1));

		return(TCL_ERROR);
	}

	path_obj = objv[1];
	path = Tcl_GetString(path_obj);

	sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (sock == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(strerror(errno), -1));

		return(TCL_ERROR);
	}

	if (strcmp(path, "-server") == 0) {
		if (objc != 4) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::socket_unix -server command path\"", -1));

			close(sock);

			return(TCL_ERROR);
		}

		command_obj = objv[2];
		path_obj = objv[3];

		path = Tcl_GetString(path_obj);

		retval = tuapi_socket_unix_server(cd, interp, sock, path, command_obj);
	} else {
		if (objc != 2) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::socket_unix path\"", -1));

			close(sock);

			return(TCL_ERROR);
		}

		retval = tuapi_socket_unix_client(cd, interp, sock, path);
	}

	if (retval != TCL_OK) {
		close(sock);
	}

	return(retval);
}
#else
static int tuapi_socket_unix(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("not implemented", -1));
	return(TCL_ERROR)
}
#endif

static int tuapi_tsmf_start_svc(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
	struct timeval select_timeout;
	Tcl_WideInt umask_val, timeout_val, uid_val, gid_val;
	Tcl_Obj *filename_obj, *env_obj, *logfile_obj, **env_entry_objv, *cwd_obj, *umask_obj, *uid_obj, *gid_obj;
	Tcl_Obj *sri_obj, *timeout_obj;
	pid_t child, child_pgid = -1, waitpid_ret;
	ssize_t read_ret;
	time_t currtime;
	char *argv[3], *envv[512];
	char *logfile, *filename, *cwd;
	char logmsg[2048];
	fd_set read_fdset;
	int pipe_ret, setsid_ret, execve_ret, tcl_ret, select_ret, chdir_ret;
	int null_fd, log_fd, tmp_fd, max_fd;
	int env_entry_objc;
	int fds[2], fd;
	int status;
	int idx;

	/* 1. Parse arguments */
	/* 1.a. Ensure the correct number of arguments were passed */
	if (objc != 10) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be \"::tuapi::syscall::tsmf_start_svc sri filename logfile env cwd umask uid gid timeout\"", -1));

		return(TCL_ERROR);
	}

	/* 1.b. Identify Tcl_Objs to use for each argument */
	sri_obj = objv[1];
	filename_obj = objv[2];
	logfile_obj = objv[3];
	env_obj = objv[4];
	cwd_obj = objv[5];
	umask_obj = objv[6];
	uid_obj = objv[7];
	gid_obj = objv[8];
	timeout_obj = objv[9];

	/* 1.c. Store string arguments */
	filename = Tcl_GetString(filename_obj);
	logfile = Tcl_GetString(logfile_obj);
	cwd = Tcl_GetString(cwd_obj);

	/* 1.d. Integer objects */
	tcl_ret = Tcl_GetWideIntFromObj(interp, umask_obj, &umask_val);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	tcl_ret = Tcl_GetWideIntFromObj(interp, timeout_obj, &timeout_val);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	tcl_ret = Tcl_GetWideIntFromObj(interp, uid_obj, &uid_val);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	tcl_ret = Tcl_GetWideIntFromObj(interp, gid_obj, &gid_val);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	/* 1.e. Process environment */
	tcl_ret = Tcl_ListObjGetElements(interp, env_obj, &env_entry_objc, &env_entry_objv);
	if (tcl_ret != TCL_OK) {
		return(tcl_ret);
	}

	for (idx = 0; idx < MIN(env_entry_objc, sizeof(envv) / sizeof(envv[0]) - 1); idx++) {
		envv[idx] = Tcl_GetString(env_entry_objv[idx]);
	}
	envv[idx] = NULL;

	/* 2. Create a pipe for communication between the processes */
	pipe_ret = pipe(fds);
	if (pipe_ret != 0) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("pipe failed", -1));

		return(TCL_ERROR);
	}

	/* 3. Fork into a new process */
	child = fork();
	if (child == -1) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj("fork failed", -1));

		return(TCL_ERROR);
	}

	if (child != 0) {
		/* 4.parent. Get PGID from child */
		/* 4.parent.a. Close write end of pipe -- we are read-only */
		close(fds[1]);
		fd = fds[0];

		/* 4.parent.b. Read process group ID of child from pipe */
		select_timeout.tv_sec = timeout_val;
		select_timeout.tv_usec = 0;

		FD_ZERO(&read_fdset);
		FD_SET(fd, &read_fdset);

		select_ret = select(fd + 1, &read_fdset, NULL, NULL, &select_timeout);
		if (select_ret == 0) {
			/* On timeout, terminate starting process */
			child_pgid = getpgid(child);
			if (child_pgid != -1) {
				kill(-child_pgid, SIGKILL);
			}

			Tcl_SetObjResult(interp, Tcl_NewStringObj("timeout", -1));

			return(TCL_ERROR);
		}

		if (select_ret > 0) {
			read_ret = read(fd, &child_pgid, sizeof(child_pgid));
		}

		/* 4.parent.c. Close read end of pipe */
		close(fd);

		/* 4.parent.d. Verify read was meaningful */
		if (read_ret != sizeof(child_pgid)) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("failed to communicate with started service", -1));

			return(TCL_ERROR);
		}

		/* 4.parent.e. If the PGID given is actually an error, return error */
		if (child_pgid == -1) {
			Tcl_SetObjResult(interp, Tcl_NewStringObj("service failed to start", -1));

			return(TCL_ERROR);
		}

		/* 4.parent.f. Return PGID to Tcl */
		Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) child_pgid));

		return(TCL_OK);
	}

	/* 4.child.a. Close read end of pipe -- we only write to it */
	close(fds[0]);
	fd = fds[1];

	/* 5. Create a new session */
	setsid_ret = setsid();
	if (setsid_ret == -1) {
		write(fd, &child_pgid, sizeof(child_pgid));

		_exit(0);
	}

	/* 6. Setup environment */
	/* 6.a. Set umask */
	umask(umask_val);

	/* 6.b. Set working directory */
	chdir_ret = chdir(cwd);
	if (chdir_ret != 0) {
		write(fd, &child_pgid, sizeof(child_pgid));

		_exit(0);
	}

	/* 6.c. Open log file for stderr and stdout */
	log_fd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

	/* 6.d. Open "/dev/null" for stdin */
	null_fd = open("/dev/null", O_RDONLY);
	if (null_fd < 0 || log_fd <0) {
		write(fd, &child_pgid, sizeof(child_pgid));

		_exit(0);
	}

	/* 6.e. Redirect stdin, stdout, and stderr to null, logs */
	dup2(null_fd, STDIN_FILENO);
	dup2(log_fd, STDOUT_FILENO);
	dup2(log_fd, STDERR_FILENO);

	close(null_fd);
	close(log_fd);

	/* 6.f. Close stray file descriptors */
	max_fd = MAX(MAX(MAX(1024, STDIN_FILENO), STDOUT_FILENO), STDERR_FILENO);
	for (tmp_fd = 0; tmp_fd < max_fd; tmp_fd++) {
		if (tmp_fd == STDIN_FILENO || tmp_fd == STDOUT_FILENO || tmp_fd == STDERR_FILENO) {
			continue;
		}

		if (tmp_fd == fd) {
			continue;
		}

		close(tmp_fd);
	}

	/* 6.g. Switch to appropriate user/group */
	/* 6.g.i. Group */
	setgid(gid_val);

	/* 6.g.ii. User */
	setuid(uid_val);

	/* 7. Create a new process to actually spawn the process */
	child = fork();
	if (child == -1) {
		write(fd, &child_pgid, sizeof(child_pgid));

		_exit(0);
	}

	if (child != 0) {
		/* 7.parent.a. Wait for child process to terminate and collect status */
		waitpid_ret = waitpid(child, &status, 0);
		if (waitpid_ret == -1) {
			status = -1;
		}

		/* 7.parent.b. Set PGID (if successful, -1 otherwise) to pass back to TSMF */
		if (status == 0) {
			child_pgid = getpgid(getpid());
		}
		write(fd, &child_pgid, sizeof(child_pgid));

		close(fd);

		/* 7.parent.c. Write log of result */
		/* Note: We avoid ANSI I/O here in case there is already something in the buffer */
		currtime = time(NULL);
		strftime(logmsg, sizeof(logmsg), "[ %b %e %H:%M:%S ", localtime(&currtime));
		write(STDERR_FILENO, logmsg, strlen(logmsg));

		snprintf(logmsg, sizeof(logmsg), "Method \"start\" exited with status %i ]\n", WEXITSTATUS(status));
		write(STDERR_FILENO, logmsg, strlen(logmsg));

		_exit(0);
	}
	
	/* 7.child.a. Close channel to parent */
	close(fd);

	/* 8. Log attempt to run start method */
	currtime = time(NULL);
	strftime(logmsg, sizeof(logmsg), "[ %b %e %H:%M:%S ", localtime(&currtime));
	write(STDERR_FILENO, logmsg, strlen(logmsg));

	snprintf(logmsg, sizeof(logmsg), "Executing start method (\"%s\") ]\n", filename);
	write(STDERR_FILENO, logmsg, strlen(logmsg));

	/* 9. execve() new image */
	argv[0] = filename;
	argv[1] = "start";
	argv[2] = NULL;
	execve_ret = execve(filename, argv, envv);

	/* 10. Abort if something has gone wrong */
	_exit(execve_ret);

	/* Handle lint-ness */
	return(TCL_ERROR);
	sri_obj = sri_obj;
}

int Tuapi_Init(Tcl_Interp *interp) {
#ifdef USE_TCL_STUBS
	const char *tclInitStubs_ret;

	/* Initialize Stubs */
	tclInitStubs_ret = Tcl_InitStubs(interp, "8.4", 0);
	if (!tclInitStubs_ret) {
		return(TCL_ERROR);
	}
#endif

	/* Kernel maintenance related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::insmod", tuapi_insmod, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::rmmod", tuapi_rmmod, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::lsmod", tuapi_lsmod, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::hostname", tuapi_hostname, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::domainname", tuapi_domainname, NULL, NULL);

	/* Block or char device related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::losetup", tuapi_losetup, NULL, NULL);

	/* Filesystem related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::mount", tuapi_mount, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::umount", tuapi_umount, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::swapon", tuapi_swapon, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::swapoff", tuapi_swapoff, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::mknod", tuapi_mknod, NULL, NULL);

	/* Process related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::getuid", tuapi_getuid, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::chroot", tuapi_chroot, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::pivot_root", tuapi_pivot_root, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::kill", tuapi_kill, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::ps", tuapi_ps, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::execve", tuapi_execve, NULL, NULL);

	/* Network related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::ifconfig", tuapi_ifconfig, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::route", tuapi_route, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::brctl", tuapi_brctl, NULL, NULL);
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::vconfig", tuapi_vconfig, NULL, NULL);

	/* Needed commands for basic services Tcl lacks */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::socket_unix", tuapi_socket_unix, NULL, NULL);

	/* Service (TSMF) related commands */
	Tcl_CreateObjCommand(interp, "::tuapi::syscall::tsmf_start_svc", tuapi_tsmf_start_svc, NULL, NULL);

	/* Internal functions */
	Tcl_CreateObjCommand(interp, "::tuapi::internal::hash", tuapi_internalproc_simplehash, NULL, NULL);

	/* Define constants */
	/** Create parent namespace **/
	Tcl_CreateNamespace(interp, "::tuapi::const", NULL, NULL);

	/** Define constants, for real **/
	Tcl_ObjSetVar2(interp, Tcl_NewStringObj("::tuapi::const::HOST_NAME_MAX", -1), NULL, Tcl_NewWideIntObj(HOST_NAME_MAX), TCL_GLOBAL_ONLY);

	/* Create high-level user functions */
	Tcl_Eval(interp,
#include "tuapi.tcl.h" 
	);

	Tcl_PkgProvide(interp, "tuapi", "0.3");

	return(TCL_OK);
}