shithub: npe

Download patch

ref: fd731d3c833d9241fdff618b6c949453fb5c0f31
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Mon Mar 15 07:38:36 EDT 2021

dump it to git so I do not lose anything

diff: cannot open b/SDL2//null: file does not exist: 'b/SDL2//null' diff: cannot open b/sys//null: file does not exist: 'b/sys//null'
--- /dev/null
+++ b/README.md
@@ -1,0 +1,14 @@
+# npe
+
+"Native Porting Environment" for 9front, pronounced "nope".
+
+*WIP*
+
+The project tries to provide a minimalistic (and mostly
+non-conformant) POSIX environment with several additional libraries
+(cut-down version of SDL2 from scratch for now) to ease up porting of
+software to 9front.  The goal is to be able to build and run
+non-native GUI software with very minimal changes, if any.
+
+Unlike APE, NPE is fully native and is not trying to hide any of the
+native Plan 9 APIs.
--- /dev/null
+++ b/SDL2/SDL.h
@@ -1,0 +1,431 @@
+#ifndef _SDL_h_
+#define _SDL_h_
+
+#include "plan9.h"
+#include <keyboard.h>
+
+typedef int SDL_AudioDeviceID;
+typedef struct SDL_AudioSpec SDL_AudioSpec;
+typedef struct SDL_Window SDL_Window;
+typedef struct SDL_Renderer SDL_Renderer;
+typedef struct SDL_Texture SDL_Texture;
+typedef struct SDL_Surface SDL_Surface;
+typedef struct SDL_Rect SDL_Rect;
+typedef int SDL_Keycode;
+typedef int SDL_Scancode;
+typedef int SDL_Keymod;
+typedef struct SDL_Event SDL_Event;
+typedef u8int Uint8;
+typedef u16int Uint16;
+typedef u32int Uint32;
+typedef int SDL_BlendMode;
+typedef struct SDL_Thread SDL_Thread;
+typedef int (*SDL_ThreadFunction)(void *);
+typedef struct SDL_Cursor SDL_Cursor;
+typedef struct SDL_PixelFormat SDL_PixelFormat;
+typedef struct SDL_Point SDL_Point;
+typedef int SDL_RendererFlip;
+typedef struct SDL_DisplayMode SDL_DisplayMode;
+
+#pragma incomplete SDL_Cursor
+#pragma incomplete SDL_PixelFormat
+#pragma incomplete SDL_Renderer
+#pragma incomplete SDL_Texture
+#pragma incomplete SDL_Thread
+#pragma incomplete SDL_Window
+
+typedef enum {SDL_FALSE, SDL_TRUE} SDL_bool;
+#define SDLCALL
+
+void SDL_StopTextInput(void);
+SDL_bool SDL_HasSSE(void);
+SDL_bool SDL_HasSSE2(void);
+int SDL_Init(int);
+int SDL_EventState(Uint32, int);
+SDL_Keymod SDL_GetModState(void);
+char *SDL_GetAudioDeviceName(int, SDL_bool);
+int SDL_GetNumAudioDevices(int iscapture);
+int SDL_ShowCursor(int toggle);
+void SDL_LockAudioDevice(SDL_AudioDeviceID);
+void SDL_UnlockAudioDevice(SDL_AudioDeviceID);
+void SDL_PauseAudioDevice(SDL_AudioDeviceID, SDL_bool);
+void SDL_CloseAudioDevice(SDL_AudioDeviceID);
+SDL_AudioDeviceID SDL_OpenAudioDevice(char *, int, SDL_AudioSpec *, SDL_AudioSpec *, u32int);
+u64int SDL_GetPerformanceFrequency(void);
+u64int SDL_GetPerformanceCounter(void);
+char *SDL_GetError(void);
+SDL_Thread *SDL_CreateThread(SDL_ThreadFunction, char *, void *);
+void SDL_DetachThread(SDL_Thread *);
+char *SDL_GetClipboardText(void);
+int SDL_SetClipboardText(char *);
+void SDL_SetThreadPriority(int);
+SDL_bool SDL_HasClipboardText(void);
+int SDL_PollEvent(SDL_Event *event);
+void SDL_RestoreWindow(SDL_Window *window);
+void SDL_RaiseWindow(SDL_Window *window);
+int SDL_UpdateTexture(SDL_Texture *texture, SDL_Rect *rect, void *pixels, int pitch);
+int SDL_RenderClear(SDL_Renderer *renderer);
+int SDL_GetWindowDisplayIndex(SDL_Window *window);
+void SDL_FreeSurface(SDL_Surface *surface);
+Uint32 SDL_GetGlobalMouseState(int *x, int *y);
+void SDL_Quit(void);
+void SDL_free(void *);
+SDL_Cursor *SDL_GetDefaultCursor(void);
+void SDL_SetCursor(SDL_Cursor *cursor);
+void SDL_FreeCursor(SDL_Cursor *cursor);
+SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 rm, Uint32 gm, Uint32 bm, Uint32 am);
+SDL_Surface *SDL_CreateRGBSurfaceFrom(Uint32 *pixels, int w, int h, int bpp, int pitch, Uint32 rm, Uint32 gm, Uint32 bm, Uint32 am);
+Uint32 SDL_MapRGB(SDL_PixelFormat *format, Uint8 r, Uint8 g, Uint8 b);
+int SDL_SetColorKey(SDL_Surface *surface, int flag, Uint32 key);
+int SDL_SetSurfaceRLE(SDL_Surface *surface, int flag);
+int SDL_LockSurface(SDL_Surface *surface);
+int SDL_UnlockSurface(SDL_Surface *surface);
+int SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode);
+SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y);
+int SDL_PushEvent(SDL_Event *event);
+void SDL_WarpMouseInWindow(SDL_Window *window, int x, int y);
+void SDL_RenderGetScale(SDL_Renderer *renderer, float *scaleX, float *scaleY);
+void SDL_GetWindowSize(SDL_Window *window, int *w, int *h);
+void SDL_GetWindowPosition(SDL_Window *window, int *x, int *y);
+Uint32 SDL_GetMouseState(int *x, int *y);
+SDL_bool SDL_IsTextInputActive(void);
+void SDL_StartTextInput(void);
+void SDL_Delay(Uint32 ms);
+int SDL_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Rect *srcrect, SDL_Rect *dstrect);
+void SDL_RenderPresent(SDL_Renderer *renderer);
+Uint32 SDL_GetWindowFlags(SDL_Window *window);
+int SDL_RenderSetLogicalSize(SDL_Renderer *renderer, int w, int h);
+void SDL_SetWindowSize(SDL_Window *window, int w, int h);
+int SDL_ShowSimpleMessageBox(Uint32 flags, char *title, char *message, SDL_Window *window);
+int SDL_SetWindowFullscreen(SDL_Window *window, Uint32 flags);
+void SDL_SetWindowGrab(SDL_Window *window, SDL_bool grabbed);
+void SDL_SetWindowPosition(SDL_Window *window, int x, int y);
+void SDL_DestroyTexture(SDL_Texture *texture);
+void SDL_DestroyRenderer(SDL_Renderer *renderer);
+void SDL_DestroyWindow(SDL_Window *window);
+int SDL_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode *mode);
+void SDL_SetWindowTitle(SDL_Window *window, char *title);
+int SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode);
+SDL_bool SDL_SetHint(char *name, char *value);
+SDL_Window *SDL_CreateWindow(char *title, int x, int y, int w, int h, Uint32 flags);
+SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, int index, Uint32 flags);
+int SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode);
+char *SDL_GetCurrentVideoDriver(void);
+SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, Uint32 format, int access, int w, int h);
+void SDL_EnableScreenSaver(void);
+Uint32 SDL_GetTicks(void);
+int SDL_GetRendererOutputSize(SDL_Renderer *renderer, int *w, int *h);
+int SDL_SaveBMP(SDL_Surface *s, const char *file);
+
+enum {
+	AUDIO_S16,
+	AUDIO_F32,
+
+	SDL_AUDIO_ALLOW_ANY_CHANGE = ~0,
+
+	SDL_THREAD_PRIORITY_HIGH = 1,
+
+	SDL_QUERY = -1,
+	SDL_DISABLE,
+	SDL_ENABLE,
+
+	SDL_MESSAGEBOX_ERROR = 0,
+
+	SDL_WINDOW_MINIMIZED = 1<<0,
+	SDL_WINDOW_FULLSCREEN_DESKTOP = 1<<1,
+	SDL_WINDOW_INPUT_FOCUS = 1<<2,
+	SDL_WINDOW_ALLOW_HIGHDPI = 1<<3,
+	SDL_WINDOW_SHOWN = 1<<4,
+
+	SDL_WINDOWPOS_CENTERED = -1,
+	SDL_WINDOWPOS_UNDEFINED = -2,
+
+	SDL_INIT_AUDIO = 1<<0,
+	SDL_INIT_VIDEO = 1<<1,
+	SDL_INIT_JOYSTICK = 1<<2,
+
+	SDL_BLENDMODE_NONE = 0,
+
+	SDL_FLIP_NONE = 0,
+	SDL_FLIP_HORIZONTAL,
+	SDL_FLIP_VERTICAL,
+
+	SDL_PIXELFORMAT_ARGB8888 = 0x30128888,
+
+	/* shit no one cares about */
+	SDL_TEXTUREACCESS_STREAMING = 0,
+	SDL_TEXTUREACCESS_STATIC = 0,
+	SDL_RENDERER_ACCELERATED = 0,
+	SDL_RENDERER_PRESENTVSYNC = 0,
+};
+
+enum {
+	/* these HAVE to be in this order for notes to work in ft2-clone */
+	SDL_SCANCODE_A = 0x04,
+	SDL_SCANCODE_B,
+	SDL_SCANCODE_C,
+	SDL_SCANCODE_D,
+	SDL_SCANCODE_E,
+	SDL_SCANCODE_F,
+	SDL_SCANCODE_G,
+	SDL_SCANCODE_H,
+	SDL_SCANCODE_I,
+	SDL_SCANCODE_J,
+	SDL_SCANCODE_K,
+	SDL_SCANCODE_L,
+	SDL_SCANCODE_M,
+	SDL_SCANCODE_N,
+	SDL_SCANCODE_O,
+	SDL_SCANCODE_P,
+	SDL_SCANCODE_Q,
+	SDL_SCANCODE_R,
+	SDL_SCANCODE_S,
+	SDL_SCANCODE_T,
+	SDL_SCANCODE_U,
+	SDL_SCANCODE_V,
+	SDL_SCANCODE_W,
+	SDL_SCANCODE_X,
+	SDL_SCANCODE_Y,
+	SDL_SCANCODE_Z,
+	SDL_SCANCODE_1,
+	SDL_SCANCODE_2,
+	SDL_SCANCODE_3,
+	SDL_SCANCODE_4,
+	SDL_SCANCODE_5,
+	SDL_SCANCODE_6,
+	SDL_SCANCODE_7,
+	SDL_SCANCODE_8,
+	SDL_SCANCODE_9,
+	SDL_SCANCODE_0,
+	SDL_SCANCODE_ENTER,
+	SDL_SCANCODE_ESCAPE,
+	SDL_SCANCODE_BACKSPACE,
+	SDL_SCANCODE_TAB,
+	SDL_SCANCODE_SPACE,
+	SDL_SCANCODE_MINUS,
+	SDL_SCANCODE_EQUALS,
+	SDL_SCANCODE_LEFTBRACKET,
+	SDL_SCANCODE_RIGHTBRACKET,
+	SDL_SCANCODE_BACKSLASH,
+	SDL_SCANCODE_NONUSHASH,
+	SDL_SCANCODE_SEMICOLON,
+	SDL_SCANCODE_APOSTROPHE,
+	SDL_SCANCODE_GRAVE,
+	SDL_SCANCODE_SLASH,
+	SDL_SCANCODE_CAPSLOCK,
+
+	SDL_SCANCODE_RIGHT = 0x4f,
+	SDL_SCANCODE_LEFT,
+	SDL_SCANCODE_DOWN,
+	SDL_SCANCODE_UP,
+	SDL_SCANCODE_NUMLOCKCLEAR,
+	SDL_SCANCODE_KP_DIVIDE,
+	SDL_SCANCODE_KP_MULTIPLY,
+	SDL_SCANCODE_KP_MINUS,
+	SDL_SCANCODE_KP_PLUS,
+	SDL_SCANCODE_KP_ENTER,
+	SDL_SCANCODE_KP_1,
+	SDL_SCANCODE_KP_2,
+	SDL_SCANCODE_KP_3,
+	SDL_SCANCODE_KP_4,
+	SDL_SCANCODE_KP_5,
+	SDL_SCANCODE_KP_6,
+	SDL_SCANCODE_KP_7,
+	SDL_SCANCODE_KP_8,
+	SDL_SCANCODE_KP_9,
+	SDL_SCANCODE_KP_0,
+	SDL_SCANCODE_KP_PERIOD,
+	SDL_SCANCODE_NONUSBACKSLASH,
+
+	SDL_SCANCODE_MUTE = 0x7f,
+	SDL_SCANCODE_VOLUMEUP,
+	SDL_SCANCODE_VOLUMEDOWN,
+
+	SDL_SCANCODE_AUDIOMUTE = 0x106,
+
+	SDLK_a = 'a',
+	SDLK_b,
+	SDLK_c,
+	SDLK_d,
+	SDLK_e,
+	SDLK_f,
+	SDLK_g,
+	SDLK_h,
+	SDLK_i,
+	SDLK_j,
+	SDLK_k,
+	SDLK_l,
+	SDLK_m,
+	SDLK_n,
+	SDLK_o,
+	SDLK_p,
+	SDLK_q,
+	SDLK_r,
+	SDLK_s,
+	SDLK_t,
+	SDLK_u,
+	SDLK_v,
+	SDLK_w,
+	SDLK_x,
+	SDLK_y,
+	SDLK_z,
+	SDLK_0 = '0',
+	SDLK_1,
+	SDLK_2,
+	SDLK_3,
+	SDLK_4,
+	SDLK_5,
+	SDLK_6,
+	SDLK_7,
+	SDLK_8,
+	SDLK_9,
+	SDLK_DELETE = Kdel,
+	SDLK_RETURN = '\n',
+	SDLK_ESCAPE = Kesc,
+	SDLK_LESS = '<',
+	SDLK_SPACE = ' ',
+	SDLK_TAB = '\t',
+	SDLK_LEFT = Kleft,
+	SDLK_RIGHT = Kright,
+	SDLK_DOWN = Kdown,
+	SDLK_UP = Kup,
+	SDLK_F1 = KF|1,
+	SDLK_F2,
+	SDLK_F3,
+	SDLK_F4,
+	SDLK_F5,
+	SDLK_F6,
+	SDLK_F7,
+	SDLK_F8,
+	SDLK_F9,
+	SDLK_F10,
+	SDLK_F11,
+	SDLK_F12,
+	SDLK_INSERT = Kins,
+	SDLK_PAGEUP = Kpgup,
+	SDLK_PAGEDOWN = Kpgdown,
+	SDLK_HOME = Khome,
+	SDLK_END = Kend,
+	SDLK_BACKSPACE = Kbs,
+	SDLK_MINUS = '-',
+	SDLK_PLUS = '+',
+	SDLK_EQUALS = '=',
+	SDLK_UNDERSCORE = '_',
+
+	SDLK_LALT = Kalt,
+	SDLK_RALT = Kaltgr, /* FIXME what about keyboards without it? */
+	/* FIXME no distinction */
+	SDLK_LSHIFT = Kshift,
+	SDLK_RSHIFT = SDLK_LSHIFT,
+	SDLK_LCTRL = Kctl,
+	SDLK_RCTRL = '>', /* FIXME this is a hack */
+
+	/* FIXME not bound to anything */
+	SDLK_UNKNOWN = -99999,
+	SDLK_CAPSLOCK,
+	SDLK_KP_ENTER,
+
+	/* FIXME no distinction */
+	KMOD_LSHIFT = 1<<0,
+	KMOD_RSHIFT = KMOD_LSHIFT,
+	KMOD_LCTRL = 1<<1,
+	KMOD_RCTRL = KMOD_LCTRL,
+	KMOD_LALT = 1<<2,
+	KMOD_RALT = KMOD_LALT,
+	KMOD_LGUI = 1<<3,
+	KMOD_RGUI = KMOD_LGUI,
+
+	SDL_KEYDOWN = 0,
+	SDL_KEYUP,
+	SDL_MOUSEBUTTONDOWN,
+	SDL_MOUSEBUTTONUP,
+	SDL_MOUSEWHEEL,
+	SDL_MOUSEMOTION,
+	SDL_QUIT,
+	SDL_DROPFILE,
+	SDL_TEXTINPUT,
+	SDL_WINDOWEVENT,
+	SDL_WINDOWEVENT_HIDDEN,
+	SDL_WINDOWEVENT_SHOWN,
+	SDL_WINDOWEVENT_MINIMIZED,
+	SDL_WINDOWEVENT_FOCUS_LOST,
+	SDL_WINDOWEVENT_EXPOSED,
+
+	SDL_BUTTON_LEFT = 0,
+	SDL_BUTTON_MIDDLE = 1,
+	SDL_BUTTON_RIGHT = 2,
+
+	SDL_BUTTON_LMASK = 1<<SDL_BUTTON_LEFT,
+	SDL_BUTTON_MMASK = 1<<SDL_BUTTON_MIDDLE,
+	SDL_BUTTON_RMASK = 1<<SDL_BUTTON_RIGHT,
+};
+
+struct SDL_AudioSpec {
+	void (*callback)(void *, Uint8 *, int);
+	void *userdata;
+	int freq;
+	int format;
+	int channels;
+	int samples;
+};
+
+struct SDL_Point {
+	int x, y;
+};
+
+struct SDL_Rect {
+	int x, y, w, h;
+};
+
+struct SDL_Surface {
+	SDL_PixelFormat *format;
+	SDL_Rect clip_rect;
+	Uint32 flags;
+	Uint32 key;
+	int keyset;
+	int w, h;
+	int pitch;
+	uchar pixels[];
+};
+
+struct SDL_Event {
+	int type;
+	union {
+		struct {
+			int event;
+		}window;
+		struct {
+			struct {
+				SDL_Scancode scancode;
+				SDL_Keycode sym;
+			}keysym;
+			int repeat;
+		}key;
+		struct {
+			int x, y;
+			int button;
+		}button;
+		struct {
+			int x, y;
+		}motion;
+		struct {
+			char text[UTFmax+1];
+		}text;
+		struct {
+			int x, y;
+		}wheel;
+		struct {
+			char *file;
+		}drop;
+	};
+};
+
+struct SDL_DisplayMode
+{
+	int format;
+	int w;
+	int h;
+	int refresh_rate;
+};
+
+#endif
--- /dev/null
+++ b/alloca.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/assert.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/ctype.h
@@ -1,0 +1,7 @@
+#ifndef _ctype_h_
+#define _ctype_h_
+
+#include "plan9.h"
+#include </sys/include/ctype.h>
+
+#endif
--- /dev/null
+++ b/dirent.c
@@ -1,0 +1,59 @@
+#include <dirent.h>
+
+struct DIR {
+	Dir *d;
+	struct dirent de;
+	int fd;
+	long n; /* number of entries in d */
+	long i; /* current entry to return from readdir */
+};
+
+struct dirent *
+readdir(DIR *d)
+{
+	if(d->i >= d->n){
+		free(d->d);
+		if((d->n = dirread(d->fd, &d->d)) <= 0)
+			return nil;
+		d->i = 0;
+	}
+
+	d->de.d_type = (d->d[d->i].qid.type & QTDIR) ? DT_DIR : DT_FILE;
+	d->de.d_name = d->d[d->i].name;
+	d->i++;
+
+	return &d->de;
+}
+
+DIR *
+opendir(char *name)
+{
+	Dir *dir;
+	DIR *d;
+	int fd;
+
+	dir = nil;
+	d = nil;
+	if((fd = open(name, OREAD|OCEXEC)) >= 0 &&
+	    (dir = dirfstat(fd)) != nil &&
+	    (dir->qid.type & QTDIR) != 0 &&
+	    (d = calloc(1, sizeof(*d))) != nil){
+		d->fd = fd;
+	}
+
+	free(dir);
+	if(d == nil)
+		close(fd);
+
+	return d;
+}
+
+int
+closedir(DIR *d)
+{
+	free(d->d);
+	close(d->fd);
+	free(d);
+
+	return 0;
+}
--- /dev/null
+++ b/dirent.h
@@ -1,0 +1,25 @@
+#ifndef _dirent_h_
+#define _dirent_h_
+
+#include "plan9.h"
+
+enum {
+	DT_LNK = -1,
+	DT_UNKNOWN = 0,
+	DT_FILE,
+	DT_DIR,
+};
+
+struct dirent {
+	int d_type;
+	char *d_name;
+};
+
+typedef struct DIR DIR;
+#pragma incomplete DIR
+
+DIR *opendir(char *name);
+int closedir(DIR *dirp);
+struct dirent *readdir(DIR *dirp);
+
+#endif
--- /dev/null
+++ b/iconv.c
@@ -1,0 +1,125 @@
+#include <iconv.h>
+
+enum {
+	Itranslit = 1<<0, /* no transliteration is performed :( */
+	Iignore = 1<<1, /* this option is ignored (pun TOTALLY intended) if Itranslit is set */
+
+	Tcs = 0,
+	Us,
+};
+
+struct iconv_s {
+	char to[16];
+	char from[16];
+	int fd;
+};
+
+static int
+cp2tcs(char *cp, char *tcs, int sz)
+{
+	int opt, n, i;
+	char *s;
+
+	opt = 0;
+	n = 0;
+	for(s = cp; (s = strstr(s, "//")) != nil;){
+		if(n == 0)
+			n = (int)(s - cp);
+		s += 2;
+		if(strncmp(s, "TRANSLIT", 8) == 0 && (s[8] == 0 || s[8] == '/'))
+			opt |= Itranslit;
+		else if(strncmp(s, "IGNORE", 6) == 0 && (s[6] == 0 || s[6] == '/'))
+			opt |= Iignore;
+	}
+	if(n < 1)
+		n = strlen(cp);
+
+	if(cp[0] >= '0' && cp[0] <= '9'){ /* ibm */
+		n = snprint(tcs, sz, "ibm%.*s", n, cp);
+	}else if(strncmp(cp, "ISO", 3) == 0){
+		if(cp[3] == '-' || cp[3] == '_'){
+			n--;
+			cp++;
+		}
+		n = snprint(tcs, sz, "%.*s", n-3, cp+3);
+	}else if(strncmp(cp, "CP12", 4) == 0){
+		n = snprint(tcs, sz, "windows-12%.*s", n-4, cp+4);
+	}else{ /* last chance, convert to lowercase. FIXME not all are supported, no checks are made */
+		n = snprint(tcs, sz, "%.*s", n, cp);
+		for(i = 0; i < n; i++)
+			tcs[i] = tolower(tcs[i]);
+	}
+
+	return n >= sz ? -1 : opt;
+}
+
+iconv_t
+iconv_open(char *to, char *from)
+{
+	int opt, pid, p[2];
+	iconv_t ic;
+
+	p[0] = p[1] = -1;
+	if((ic = calloc(1, sizeof(*ic))) == nil)
+		goto err;
+	if(cp2tcs(from, ic->from, sizeof(ic->from)) < 0)
+		goto err;
+	if((opt = cp2tcs(to, ic->to, sizeof(ic->to))) < 0)
+		goto err;
+	if(pipe(p) < 0)
+		goto err;
+	if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
+		dup(p[Tcs], 0);
+		dup(p[Tcs], 1);
+		close(p[0]);
+		close(p[1]);
+		close(2); /* comment it out if you need debugging */
+		execl("/bin/tcs", "tcs", "-f", ic->from, "-t", ic->to, (opt == Iignore) ? "-c" : nil, nil);
+		sysfatal("execl: %r");
+	}else if(pid < 0){
+		goto err;
+	}
+	close(p[Tcs]);
+	ic->fd = p[Us];
+
+	setmalloctag(ic, getcallerpc(&to));
+
+	return ic;
+err:
+	close(p[0]);
+	close(p[1]);
+	if(ic != nil)
+		free(ic);
+	return (iconv_t)-1;
+}
+
+void
+iconv_close(iconv_t ic)
+{
+	close(ic->fd);
+	free(ic);
+}
+
+size_t
+iconv(iconv_t ic, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)
+{
+	int n;
+
+	if(inbuf == nil || *inbuf == nil){ /* flush */
+		write(ic->fd, "", 0);
+	}else{
+		if((n = write(ic->fd, *inbuf, *inbytesleft)) < 0)
+			return -1;
+		*inbuf += n;
+		*inbytesleft -= n;
+		return 0;
+	}
+	if(outbuf == nil || *outbuf == nil) /* nothing to do */
+		return 0;
+	if((n = read(ic->fd, *outbuf, *outbytesleft)) < 0)
+		return -1;
+	*outbuf += n;
+	*outbytesleft -= n;
+
+	return n;
+}
--- /dev/null
+++ b/iconv.h
@@ -1,0 +1,14 @@
+#ifndef _iconv_h_
+#define _iconv_h_
+
+#include "plan9.h"
+#include <stdint.h>
+
+typedef struct iconv_s *iconv_t;
+#pragma incomplete struct iconv_s
+
+iconv_t iconv_open(char *to, char *from);
+void iconv_close(iconv_t);
+size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
+
+#endif
--- /dev/null
+++ b/limits.h
@@ -1,0 +1,18 @@
+#ifndef _limits_h_
+#define _limits_h_
+
+#define INT16_MAX 0x7fff
+#define INT16_MIN ((int16_t)0x8000)
+#define INT64_MIN ((int64_t)0x8000000000000000ULL)
+#define INT32_MAX 0x7fffffff
+#define INT_MAX INT32_MAX
+#define INT32_MIN (-INT32_MAX-1)
+#define INT_MIN INT32_MIN
+#define SHRT_MAX 0x7fff
+#define SHRT_MIN (-SHRT_MAX-1)
+#define SIZE_MAX 0xffffffffU
+#define UINT32_MAX 0xffffffffU
+#define UINT_MAX UINT32_MAX
+#define UINT64_MAX 0xffffffffffffffffULL
+
+#endif
--- /dev/null
+++ b/malloc.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/math.c
@@ -1,0 +1,71 @@
+#include <math.h>
+
+/* some of these were taken from musl */
+
+#define ln2o1 1.4426950408889634073599
+#define dbleps 2.2204460492503131e-16
+#define huge 1.79769313486231e+308
+
+static double toint = 1.0/dbleps;
+
+double
+round(double x)
+{
+	union {double f; u64int i;} u = {x};
+	int e = u.i >> 52 & 0x7ff;
+	double y;
+
+	if(e >= 0x3ff+52)
+		return x;
+	if(u.i >> 63)
+		x = -x;
+	if(e < 0x3ff-1)
+		return 0*u.f;
+	y = x + toint - toint - x;
+	if(y > 0.5)
+		y = y + x - 1;
+	else if(y <= -0.5)
+		y = y + x + 1;
+	else
+		y = y + x;
+	if(u.i >> 63)
+		y = -y;
+
+	return y;
+}
+
+double
+log2(double x)
+{
+	if(x == 0)
+		return -huge;
+	if(x < 0 || isNaN(x))
+		return NaN();
+
+	return log(x)*ln2o1;
+}
+
+double
+trunc(double x)
+{
+	union {double f; u64int i;} u = {x};
+	int e = (int)(u.i >> 52 & 0x7ff) - 0x3ff + 12;
+	u64int m;
+
+	if(e >= 52 + 12)
+		return x;
+	if(e < 12)
+		e = 1;
+	m = -1ULL >> e;
+	if((u.i & m) == 0)
+		return x;
+	u.i &= ~m;
+
+	return u.f;
+}
+
+double
+exp2(double x)
+{
+	return exp(x/ln2o1);
+}
--- /dev/null
+++ b/math.h
@@ -1,0 +1,18 @@
+#ifndef _math_h_
+#define _math_h_
+
+#include "plan9.h"
+
+#define sinf sin
+#define cosf cos
+#define fabsf fabs
+#define fmodf fmod
+#define ceilf ceil
+#define floorf floor
+
+double round(double);
+double log2(double);
+double trunc(double);
+double exp2(double);
+
+#endif
--- /dev/null
+++ b/plan9.c
@@ -1,0 +1,86 @@
+#include "plan9.h"
+#include <thread.h>
+#include <tos.h>
+#include <pool.h>
+
+int mainstacksize = 32*1024;
+
+int
+rm_r(char *dir)
+{
+	int pid;
+
+	if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
+		execl("/bin/rm", "rm", "-r", dir, nil);
+		sysfatal("execl: %r");
+	}
+
+	return pid < 0 ? -1 : 0;
+}
+
+/*
+ * nsec() is wallclock and can be adjusted by timesync
+ * so need to use cycles() instead, but fall back to
+ * nsec() in case we can't
+ */
+uvlong
+nanosec(void)
+{
+	static uvlong fasthz, xstart;
+	uvlong x, div;
+
+	if(fasthz == ~0ULL)
+		return nsec() - xstart;
+
+	if(fasthz == 0){
+		if(_tos->cyclefreq){
+			cycles(&xstart);
+			fasthz = _tos->cyclefreq;
+		} else {
+			xstart = nsec();
+			fasthz = ~0ULL;
+			fprint(2, "cyclefreq not available, falling back to nsec()\n");
+			fprint(2, "you might want to disable aux/timesync\n");
+			return 0;
+		}
+	}
+	cycles(&x);
+	x -= xstart;
+
+	/* this is ugly */
+	for(div = NSEC; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);
+
+	return x / (fasthz / div);
+}
+
+void
+nsleep(uvlong ns)
+{
+	uvlong start, end;
+
+	start = nanosec();
+	end = start + ns;
+	ns = start;
+	do{
+		if(end - ns > 750000000ULL)
+			sleep(70);
+		else if (end - ns > 25000000ULL)
+			sleep(20);
+		else if (end - ns > 10000000ULL)
+			sleep(1);
+		else
+			break;
+		ns = nanosec();
+	}while(ns < end);
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	int __main(int argc, char *argv[]);
+
+	setfcr(getfcr() & ~(FPINVAL|FPOVFL));
+	//mainmem->flags |= POOL_PARANOIA | POOL_ANTAGONISM;
+
+	threadexitsall(__main(argc, argv) == 0 ? nil : "error");
+}
--- /dev/null
+++ b/plan9.h
@@ -1,0 +1,18 @@
+#ifndef __plan9_h__
+#define __plan9_h__
+
+#include <u.h>
+#include <libc.h>
+#include </sys/include/stdio.h>
+
+#define PATH_MAX 256
+
+#define __attribute__(a)
+#define main __main
+#define NSEC 1000000000ULL
+
+int rm_r(char *dir);
+uvlong nanosec(void);
+void nsleep(uvlong ns);
+
+#endif
--- /dev/null
+++ b/sdl2.c
@@ -1,0 +1,1335 @@
+#include "plan9.h"
+#include <stdint.h>
+#include <tos.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <thread.h>
+#include <SDL2/SDL.h>
+#include <pool.h>
+
+typedef struct Audiodev Audiodev;
+
+enum {
+	Aout = 2,
+	Arec,
+
+	/* FIXME missing plumber→dropfile and enter()→text */
+	Ckey = 0,
+	Ckeytype,
+	Cmouse,
+	Cresize,
+	Numchan,
+
+	Rdown = 0,
+	Rup,
+	Rrepeat,
+
+	Audiobufsz = 16384,
+};
+
+struct SDL_Window {
+	int dummy;
+};
+
+struct SDL_Renderer {
+	int dummy;
+};
+
+struct SDL_Texture {
+	Memimage;
+};
+
+struct SDL_PixelFormat {
+	int format;
+};
+
+struct SDL_Cursor {
+	Image *i;
+	Image *m;
+	Point hot;
+};
+
+struct SDL_Thread {
+	SDL_ThreadFunction f;
+	char *name;
+	void *userdata;
+};
+
+static SDL_PixelFormat argb8888 = {
+	.format = SDL_PIXELFORMAT_ARGB8888,
+};
+
+struct Audiodev {
+	QLock;
+	int paused;
+	int fd;
+	int pid;
+	int mode;
+	Uint8 buf[Audiobufsz];
+	void *userdata;
+	void (*cb)(void *, Uint8 *, int);
+};
+
+/* FIXME extra USB audio devices? */
+static Audiodev au[4] = {
+	[0] = {.fd = -1, .mode = -1},
+	[1] = {.fd = -1, .mode = -1},
+	[Aout] = {.fd = -1, .pid = -1, .mode = OWRITE},
+	[Arec] = {.fd = -1, .pid = -1, .mode = OREAD},
+};
+
+static SDL_Window onewin;
+static SDL_Renderer oneren;
+static int kmod;
+static Mouse mouse, oldmouse;
+static Rune rune;
+static Mousectl *mctl;
+static Keyboardctl kctl;
+static Memimage *back;
+static u8int *backcopy;
+static Image *front;
+static int logiw, logih;
+static int physw, physh;
+static int forceredraw = 1;
+static SDL_Cursor *oldcursor, *cursor;
+static int mouseredraw = 0;
+static int showcursor = SDL_ENABLE;
+static int textinput;
+
+static Cursor nocursor = {
+	{0, 0},
+	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	},
+	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	},
+};
+
+static Alt salt[Numchan+1] = {
+	[Ckey] = { nil, &rune, CHANRCV },
+	[Ckeytype] = { nil, nil, CHANNOP },
+	[Cmouse] = { nil, &mouse, CHANRCV },
+	[Cresize] = { nil, nil, CHANRCV },
+	[Numchan] = { nil, nil, CHANNOBLK },
+};
+
+static void
+kbdproc(void *)
+{
+	char buf[128], buf2[128], *s;
+	int kfd, n, kbin, t;
+	Rune r, o;
+
+	threadsetname("kbdproc");
+	if((kfd = open("/dev/kbd", OREAD|OCEXEC)) < 0)
+		sysfatal("/dev/kbd: %r");
+	kbin = open("/dev/kbin", OWRITE|OCEXEC);
+
+	buf2[0] = 0;
+	buf2[1] = 0;
+	buf[0] = 0;
+	kmod = 0;
+	o = 0;
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			n = read(kfd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+
+		switch(buf[0]){
+		case 'c':
+			if(chartorune(&r, buf+1) > 0 && r != Runeerror){
+				t = Rrepeat;
+				if(!textinput)
+					r = tolowerrune(r);
+				if(textinput || o != r){
+					send(salt[Ckey].c, &r);
+					send(salt[Ckeytype].c, &t);
+				}
+				o = 0;
+			}
+		default:
+			continue;
+
+		case 'k':
+			s = buf+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf2+1, r) == nil){
+					if(r == Kalt){
+						/* magic trick: write Alt scancode to disable the "compose" mode */
+						/* FIXME: does this work in both native AND drawterm? */
+						write(kbin, "\x46", 1);
+						kmod |= KMOD_LALT;
+					}else if (r == Kshift)
+						kmod |= KMOD_LSHIFT;
+					else if(r == Kctl)
+						kmod |= KMOD_LCTRL;
+					else{
+						t = Rdown;
+						send(salt[Ckey].c, &r);
+						send(salt[Ckeytype].c, &t);
+						o = r;
+					}
+				}
+			}
+			break;
+
+		case 'K':
+			s = buf2+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf+1, r) == nil){
+					if(r == Kalt)
+						kmod &= ~KMOD_LALT;
+					else if(r == Kshift)
+						kmod &= ~KMOD_LSHIFT;
+					else if(r == Kctl)
+						kmod &= ~KMOD_LCTRL;
+					else{
+						t = Rup;
+						send(salt[Ckey].c, &r);
+						send(salt[Ckeytype].c, &t);
+					}
+				}
+			}
+			break;
+		}
+		strcpy(buf2, buf);
+	}
+
+	threadexits(nil);
+}
+
+int
+SDL_Init(int mask)
+{
+	/* FIXME actually use the mask? */
+	USED(mask);
+
+	if(memimageinit() < 0)
+		goto err;
+	if(initdraw(nil, nil, "ft2-clone") < 0)
+		goto err;
+	if((mctl = initmouse(nil, screen)) == nil)
+		goto err;
+
+	salt[Ckey].c = chancreate(sizeof(Rune), 20);
+	salt[Ckeytype].c = chancreate(sizeof(int), 20);
+	salt[Cmouse].c = mctl->c;
+	salt[Cresize].c = mctl->resizec;
+	kctl.c = salt[Ckey].c; /* for enter() */
+
+	if(salt[Ckey].c == nil || salt[Ckeytype].c == nil || proccreate(kbdproc, nil, 4096) < 0)
+		goto err;
+
+	return 0;
+err:
+	werrstr("SDL_Init: %r");
+	return -1;
+}
+
+int
+SDL_EventState(Uint32, int)
+{
+	return 0;
+}
+
+SDL_Keymod
+SDL_GetModState(void)
+{
+	return kmod;
+}
+
+int
+SDL_ShowCursor(int toggle)
+{
+	if(toggle == SDL_QUERY)
+		return showcursor;
+
+	showcursor = toggle == SDL_ENABLE;
+	setcursor(mctl, (cursor == nil && showcursor) ? nil : &nocursor);
+
+	return showcursor;
+}
+
+int
+SDL_GetNumAudioDevices(int iscapture)
+{
+	/* FIXME look for extra USB devices? */
+	USED(iscapture);
+	return 1;
+}
+
+char *
+SDL_GetAudioDeviceName(int index, int iscapture)
+{
+	/* FIXME look for extra USB devices? */
+	USED(index); USED(iscapture);
+	return "/dev/audio";
+}
+
+void
+SDL_LockAudioDevice(SDL_AudioDeviceID id)
+{
+	qlock(&au[id]);
+}
+
+void
+SDL_UnlockAudioDevice(SDL_AudioDeviceID id)
+{
+	qunlock(&au[id]);
+}
+
+static void
+audiothread(void *p)
+{
+	Audiodev *d;
+
+	d = p;
+	threadsetname("audio %s", d == &au[Aout] ? "out" : "in");
+
+	for(;;){
+		qlock(d);
+		if(d->paused)
+			memset(d->buf, 0, sizeof(d->buf));
+		else
+			d->cb(d->userdata, d->buf, sizeof(d->buf));
+		qunlock(d);
+
+		if(write(d->fd, d->buf, sizeof(d->buf)) != sizeof(d->buf))
+			break;
+	}
+
+	threadexits(nil);
+}
+
+void
+SDL_PauseAudioDevice(SDL_AudioDeviceID id, SDL_bool pause)
+{
+	Audiodev *a;
+
+	a = &au[id];
+	if(a->paused && !pause){
+		if(a->pid < 0)
+			a->pid = proccreate(audiothread, a, mainstacksize);
+		a->paused = 0;
+	}else if(!a->paused && pause){
+		a->paused = 1;
+	}
+}
+
+SDL_AudioDeviceID
+SDL_OpenAudioDevice(char *dev, int rec, SDL_AudioSpec *want, SDL_AudioSpec *have, u32int change)
+{
+	Audiodev *a;
+	SDL_AudioDeviceID id;
+
+	/* FIXME look for extra USB devices? */
+	USED(dev);
+
+	if(change != SDL_AUDIO_ALLOW_ANY_CHANGE){ /* FIXME sampling in mono */
+		werrstr("SDL_OpenAudioDevice: changes not implemented");
+		return 0;
+	}
+
+	have->freq = 44100;
+	have->format = AUDIO_S16;
+	have->channels = 2;
+	have->samples = want->samples;
+
+	id = rec ? Arec : Aout;
+	a = &au[id];
+
+	if(a->fd < 0 && (a->fd = open("/dev/audio", a->mode|OCEXEC)) < 0){
+		werrstr("SDL_OpenAudioDevice: %r");
+		return 0;
+	}
+
+	a->userdata = want->userdata;
+	a->cb = want->callback;
+	a->paused = 1;
+
+	return id;
+}
+
+void
+SDL_CloseAudioDevice(SDL_AudioDeviceID id)
+{
+	Audiodev *a;
+
+	a = &au[id];
+	qlock(a);
+	close(a->fd);
+	a->fd = -1;
+	a->pid = -1;
+	a->userdata = nil;
+	a->cb = nil;
+	qunlock(a);
+}
+
+u64int
+SDL_GetPerformanceFrequency(void)
+{
+	return _tos->cyclefreq;
+}
+
+u64int
+SDL_GetPerformanceCounter(void)
+{
+	u64int x;
+
+	cycles(&x);
+
+	return x;
+}
+
+char *
+SDL_GetError(void)
+{
+	static char err[256];
+
+	snprint(err, sizeof(err), "%r");
+
+	return err;
+}
+
+static void
+sdlthread(void *p)
+{
+	SDL_Thread t;
+
+	t = *(SDL_Thread*)p;
+	if(t.name != nil)
+		threadsetname(t.name);
+	free(p);
+
+	threadexits(t.f(t.userdata) == 0 ? nil : "error");
+}
+
+SDL_Thread *
+SDL_CreateThread(SDL_ThreadFunction f, char *name, void *userdata)
+{
+	SDL_Thread *t;
+
+	if((t = calloc(1, sizeof(*t))) == nil)
+		return nil;
+
+	t->f = f;
+	t->name = name;
+	t->userdata = userdata;
+
+	if(proccreate(sdlthread, t, mainstacksize) < 0){
+		free(t);
+		t = nil;
+	}
+
+	return t;
+}
+
+void
+SDL_DetachThread(SDL_Thread *)
+{
+}
+
+static void *
+readfile(char *path, int *got)
+{
+	void *data, *data2;
+	int f, n, r, sz;
+
+	if((f = open(path, OREAD|OCEXEC)) < 0)
+		return nil;
+
+	sz = 32768;
+	data = nil;
+	for(n = 0;; n += r){
+		if(sz-n < 65536){
+			sz *= 2;
+			if((data2 = realloc(data, sz)) == nil)
+				goto err;
+			data = data2;
+		}
+		if((r = read(f, (char*)data+n, sz-n-1)) < 0)
+			goto err;
+		if(r == 0)
+			break;
+	}
+
+	if(got != nil)
+		*got = n;
+	((char*)data)[n] = 0;
+
+	return data;
+err:
+	free(data);
+	close(f);
+	return nil;
+}
+
+char *
+SDL_GetClipboardText(void)
+{
+	return readfile("/dev/snarf", nil);
+}
+
+int
+SDL_SetClipboardText(char *s)
+{
+	int f, n;
+
+	n = -1;
+	if((f = open("/dev/snarf", OWRITE|OTRUNC|OCEXEC)) >= 0){
+		n = strlen(s);
+		n = write(f, s, n) == n ? 0 : -1;
+		close(f);
+	}
+
+	if(n != 0)
+		werrstr("SDL_SetClipboardText: %r");
+
+	return n;
+}
+
+SDL_Texture *
+SDL_CreateTexture(SDL_Renderer *, Uint32 format, int, int w, int h)
+{
+	SDL_Texture *t;
+
+	if(format != SDL_PIXELFORMAT_ARGB8888){
+		werrstr("SDL_CreateTexture: only SDL_PIXELFORMAT_ARGB8888 is supported");
+		goto err;
+	}
+	if((t = (SDL_Texture*)allocmemimage(Rect(0, 0, w, h), XRGB32)) == nil) /* FIXME ARGB32? */
+		goto err;
+	memfillcolor(t, DTransparent);
+
+	return t;
+err:
+	werrstr("SDL_CreateTexture: %r");
+	return nil;
+}
+
+int
+SDL_UpdateTexture(SDL_Texture *t, SDL_Rect *re, void *pixels, int pitch)
+{
+	Rectangle r;
+
+	r = re ? Rect(re->x, re->y, re->x+re->w, re->y+re->h) : t->r;
+	/* FIXME non-ARGB8888 */
+	/* FIXME pitch */ USED(pitch);
+	if(loadmemimage(t, r, pixels, Dx(r)*Dy(r)*4) < 0){
+		werrstr("SDL_UpdateTexture: %r");
+		return -1;
+	}
+
+	return 0;
+}
+
+int
+SDL_RenderClear(SDL_Renderer *)
+{
+	if(back != nil)
+		memfillcolor(back, DBlack);
+
+	return 0;
+}
+
+int
+SDL_GetWindowDisplayIndex(SDL_Window *)
+{
+	return 0;
+}
+
+void
+SDL_Quit(void)
+{
+	threadexitsall(nil);
+}
+
+void
+SDL_free(void *p)
+{
+	free(p);
+}
+
+SDL_Surface *
+SDL_CreateRGBSurface(Uint32, int w, int h, int bpp, Uint32 rm, Uint32 gm, Uint32 bm, Uint32 am)
+{
+	SDL_Surface *s;
+	int n;
+
+	USED(rm, gm, bm, am); /* FIXME masks */
+
+	n = w*h*bpp/8;
+	if((s = calloc(1, sizeof(*s)+n)) == nil){
+		werrstr("SDL_CreateRGBSurface: memory");
+		return nil;
+	}
+	s->format = &argb8888;
+	s->w = w;
+	s->h = h;
+	s->pitch = w*bpp/8;
+	s->clip_rect.x = 0;
+	s->clip_rect.y = 0;
+	s->clip_rect.w = w;
+	s->clip_rect.h = h;
+
+	return s;
+}
+
+SDL_Surface *
+SDL_CreateRGBSurfaceFrom(Uint32 *pixels, int w, int h, int bpp, int pitch, Uint32 rm, Uint32 gm, Uint32 bm, Uint32 am)
+{
+	SDL_Surface *s;
+	int n;
+
+	USED(pitch); /* FIXME pitch */
+
+	if((s = SDL_CreateRGBSurface(0, w, h, bpp, rm, gm, bm, am)) == nil)
+		return nil;
+
+	n = w*h*bpp/8;
+	memmove(s->pixels, pixels, n);
+
+	return s;
+}
+
+void
+SDL_FreeSurface(SDL_Surface *surface)
+{
+	memset(surface, 0, sizeof(surface));
+	free(surface);
+}
+
+Uint32
+SDL_MapRGB(SDL_PixelFormat *, Uint8 r, Uint8 g, Uint8 b)
+{
+	return r<<24 | g<<16 | b<<8 | 0xff;
+}
+
+int
+SDL_SetColorKey(SDL_Surface *s, int flag, Uint32 key)
+{
+	s->keyset = flag;
+	s->key = key;
+	return 0;
+}
+
+SDL_Cursor *
+SDL_CreateColorCursor(SDL_Surface *s, int hot_x, int hot_y)
+{
+	SDL_Cursor *c;
+	Rectangle r;
+	uchar *m;
+	int n;
+
+	m = nil;
+	if((c = calloc(1, sizeof(*c))) == nil){
+		werrstr("memory");
+		goto err;
+	}
+
+	r = Rect(0, 0, s->w, s->h);
+	if(s->keyset){
+		if((c->m = allocimage(display, r, GREY8, 0, DTransparent)) == nil)
+			goto err;
+		if((m = malloc(s->w * s->h)) == nil)
+			goto err;
+		for(n = 0; n < s->w * s->h; n++){
+			m[n] = ((u32int*)s->pixels)[n] == s->key ? 0x00 : 0xff;
+			if(m[n] == 0)
+				((u32int*)s->pixels)[n] = 0;
+		}
+		if(loadimage(c->m, r, m, n) < 1)
+			goto err;
+		free(m);
+		m = nil;
+	}
+	if((c->i = allocimage(display, r, s->keyset ? XRGB32 : ARGB32, 0, DTransparent)) == nil)
+		goto err;
+	n = s->w * s->h * 4; /* FIXME non-ARGB8888 */
+	if(loadimage(c->i, r, s->pixels, n) < 1)
+		goto err;
+
+	c->hot = Pt(hot_x, hot_y);
+
+	return c;
+err:
+	werrstr("SDL_CreateColorCursor: %r");
+	if(c != nil){
+		freeimage(c->i);
+		freeimage(c->m);
+	}
+	free(c);
+	free(m);
+	return nil;
+}
+
+SDL_Cursor *
+SDL_GetDefaultCursor(void)
+{
+	return nil;
+}
+
+void
+SDL_SetCursor(SDL_Cursor *c)
+{
+	if(cursor != c){
+		cursor = c;
+		mouseredraw = 1;
+		setcursor(mctl, (cursor == nil && showcursor) ? nil : &nocursor);
+	}
+}
+
+void
+SDL_FreeCursor(SDL_Cursor *c)
+{
+	freeimage(c->i);
+	free(c);
+	if(cursor == c){
+		oldcursor = nil;
+		cursor = nil;
+	}
+}
+
+static int
+rune2scancode(Rune r)
+{
+	if(r >= 'a' && r <= 'z')
+		return r - 'a' + SDL_SCANCODE_A;
+	if(r >= '1' && r <= '9')
+		return r - '1' + SDL_SCANCODE_1;
+	if(r == '0')  return SDL_SCANCODE_0;
+	if(r == '\n') return SDL_SCANCODE_ENTER;
+	if(r == Kesc) return SDL_SCANCODE_ESCAPE;
+	if(r == Kbs)  return SDL_SCANCODE_BACKSPACE;
+	if(r == '\t') return SDL_SCANCODE_TAB;
+	if(r == ' ')  return SDL_SCANCODE_SPACE;
+	if(r == '-')  return SDL_SCANCODE_MINUS;
+	if(r == '=')  return SDL_SCANCODE_EQUALS;
+	if(r == '[')  return SDL_SCANCODE_LEFTBRACKET;
+	if(r == ']')  return SDL_SCANCODE_RIGHTBRACKET;
+	if(r == '\\') return SDL_SCANCODE_BACKSLASH;
+	if(r == ';')  return SDL_SCANCODE_SEMICOLON;
+	if(r == '\'') return SDL_SCANCODE_APOSTROPHE;
+	if(r == '/')  return SDL_SCANCODE_SLASH;
+
+	if(r == Kright) return SDL_SCANCODE_RIGHT;
+	if(r == Kleft) return SDL_SCANCODE_LEFT;
+	if(r == Kdown) return SDL_SCANCODE_DOWN;
+	if(r == Kup) return SDL_SCANCODE_UP;
+
+/* FIXME
+	SDL_SCANCODE_NUMLOCKCLEAR,
+	SDL_SCANCODE_KP_DIVIDE,
+	SDL_SCANCODE_KP_MULTIPLY,
+	SDL_SCANCODE_KP_MINUS,
+	SDL_SCANCODE_KP_PLUS,
+	SDL_SCANCODE_KP_ENTER,
+	SDL_SCANCODE_KP_1,
+	SDL_SCANCODE_KP_2,
+	SDL_SCANCODE_KP_3,
+	SDL_SCANCODE_KP_4,
+	SDL_SCANCODE_KP_5,
+	SDL_SCANCODE_KP_6,
+	SDL_SCANCODE_KP_7,
+	SDL_SCANCODE_KP_8,
+	SDL_SCANCODE_KP_9,
+	SDL_SCANCODE_KP_0,
+	SDL_SCANCODE_KP_PERIOD,
+	SDL_SCANCODE_NONUSBACKSLASH,
+	SDL_SCANCODE_NONUSHASH,
+*/
+	/* FIXME there are some missing */
+
+	if(r == L'`' || r == L'´') /* FIXME this is most likely wrong */
+		return SDL_SCANCODE_GRAVE;
+
+	return r;
+}
+
+int
+SDL_PollEvent(SDL_Event *e)
+{
+	int t, down;
+
+	if(e == nil) /* FIXME need to buffer the event so it won't get lost */
+		return 0;
+
+	switch(alt(salt)){
+	case Ckey:
+		recv(salt[Ckeytype].c, &t);
+		if(textinput && rune >= 0x20 && rune <= 0x7f){
+			if(t != Rrepeat)
+				break;
+			e->type = SDL_TEXTINPUT;
+			e->text.text[runetochar(e->text.text, &rune)] = 0;
+		}else if(textinput && t != Rrepeat){
+			break;
+		}else{
+			e->type = (t == Rup) ? SDL_KEYUP : SDL_KEYDOWN;
+			e->key.repeat = !textinput && t == Rrepeat;
+			e->key.keysym.scancode = rune2scancode(rune);
+			e->key.keysym.sym = rune;
+		}
+		return 1;
+
+	case Cmouse:
+		e->motion.x = mouse.xy.x - screen->r.min.x;
+		e->motion.y = mouse.xy.y - screen->r.min.y;
+		if(!eqpt(mouse.xy, oldmouse.xy)){
+			mouseredraw = 1;
+			if(mouse.buttons == oldmouse.buttons){
+				e->type = SDL_MOUSEMOTION;
+				return 1;
+			}
+		}
+		if(mouse.buttons == oldmouse.buttons)
+			break;
+		/* FIXME there is a lot of hope for both buttons to never change at the same time */
+		if((down = (mouse.buttons & 1)) != (oldmouse.buttons & 1)){ /* left */
+			e->type = down ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
+			e->button.button = SDL_BUTTON_LEFT;
+			oldmouse.buttons = (oldmouse.buttons & ~1) | (mouse.buttons & 1);
+			return 1;
+		}
+		if((down = (mouse.buttons & 4)) != (oldmouse.buttons & 4)){ /* right */
+			e->type = down ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
+			e->button.button = SDL_BUTTON_RIGHT;
+			oldmouse.buttons = (oldmouse.buttons & ~4) | (mouse.buttons & 4);
+			return 1;
+		}
+		if(mouse.buttons & (8|16)){
+			e->type = SDL_MOUSEWHEEL;
+			e->wheel.x = 0;
+			e->wheel.y = (mouse.buttons & 8) ? -1 : 1;
+			return 1;
+		}
+		break;
+
+	case Cresize:
+		forceredraw = 1;
+		if(getwindow(display, Refnone) < 0){
+			fprint(2, "%r\n");
+			/* FIXME do something here? */
+			//threadexitsall(nil);
+		}
+		e->type = SDL_WINDOWEVENT;
+		e->window.event = SDL_WINDOWEVENT_EXPOSED;
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+SDL_PushEvent(SDL_Event *event)
+{
+	/* FIXME does it matter? */
+	USED(event);
+	return -1;
+}
+
+void
+SDL_WarpMouseInWindow(SDL_Window *, int x, int y)
+{
+	moveto(mctl, Pt(screen->r.min.x+x, screen->r.min.y+y));
+}
+
+Uint32
+SDL_GetGlobalMouseState(int *x, int *y)
+{
+	Uint32 b;
+
+	if(x != nil)
+		*x = mouse.xy.x;
+	if(y != nil)
+		*y = mouse.xy.y;
+
+	b = 0;
+	if(mouse.buttons & 1)
+		b |= SDL_BUTTON_LMASK;
+	if(mouse.buttons & 4)
+		b |= SDL_BUTTON_RMASK;
+	/* FIXME no middle button use AT ALL? */
+
+	return b;
+}
+
+Uint32
+SDL_GetMouseState(int *x, int *y)
+{
+	Uint32 b;
+
+	b = SDL_GetGlobalMouseState(nil, nil);
+	if(x != nil)
+		*x = (mouse.xy.x - screen->r.min.x) * logiw / physw;
+	if(y != nil)
+		*y = (mouse.xy.y - screen->r.min.y) * logih / physh;
+
+	return b;
+}
+
+void
+SDL_RenderGetScale(SDL_Renderer *, float *scaleX, float *scaleY)
+{
+	*scaleX = 1.0;
+	*scaleY = 1.0;
+}
+
+void
+SDL_GetWindowSize(SDL_Window *, int *w, int *h)
+{
+	/* no matter what rio decides */
+	*w = physw;
+	*h = physh;
+}
+
+void
+SDL_GetWindowPosition(SDL_Window *, int *x, int *y)
+{
+	*x = screen->r.min.x;
+	*y = screen->r.min.y;
+}
+
+SDL_bool
+SDL_IsTextInputActive(void)
+{
+	return textinput;
+}
+
+void
+SDL_StartTextInput(void)
+{
+	textinput = SDL_TRUE;
+}
+
+void
+SDL_StopTextInput(void)
+{
+	textinput = SDL_FALSE;
+}
+
+void
+SDL_Delay(Uint32 ms)
+{
+	nsleep((uvlong)ms*1000000ULL);
+}
+
+static void *
+resize(u32int *src, int iw, int ih, u32int *dst, int ow, int oh)
+{
+	int i, j, m, n;
+	u32int *d;
+
+	if(iw == ow && ih == oh)
+		return src;
+
+	d = dst;
+	n = ow/iw;
+	for(; ih > 0 && oh > 0; ih--){
+		for(i = j = 0; i < iw; i++, src++)
+			for(m = 0; m < n && j < ow; m++, j++)
+				*dst++ = *src;
+		oh--;
+		for(m = 1; m < n && oh > 0; m++){
+			memmove(dst, dst-j, j*4);
+			dst += j;
+			oh--;
+		}
+	}
+
+	return d;
+}
+
+int
+SDL_RenderCopy(SDL_Renderer *, SDL_Texture *t, SDL_Rect *sre, SDL_Rect *dre)
+{
+	Rectangle sr, dr;
+
+	if(sre != nil){
+		sr.min = Pt(sre->x, sre->y);
+		sr.max = addpt(sr.min, Pt(sre->w, sre->h));
+	}else
+		sr = t->r;
+
+	if(dre != nil){
+		dr.min = Pt(dre->x, dre->y);
+		dr.max = addpt(dr.min, Pt(dre->w, dre->h));
+	}else /* stretch */
+		dr = Rect(0, 0, logiw, logih);
+
+	if(back == nil || Dx(back->r) != logiw || Dy(back->r) != logih){
+		freememimage(back);
+		back = allocmemimage(Rect(0, 0, logiw, logih), XRGB32);
+		if(back == nil){
+			werrstr("SDL_RenderCopy: %r");
+			return -1;
+		}
+		free(backcopy);
+		backcopy = malloc(logiw*logih*4);
+	}
+
+	if(dre == nil)
+		resize((u32int*)byteaddr(t, ZP), Dx(sr), Dy(sr), (u32int*)byteaddr(back, ZP), logiw, logih);
+	else
+		memimagedraw(back, dr, t, sr.min, nil, ZP, S);
+
+	return 0;
+}
+
+void
+SDL_RenderPresent(SDL_Renderer *)
+{
+	Rectangle r, clipr;
+	static u32int *b;
+	uchar *rb;
+
+	if(!forceredraw && (forceredraw = memcmp(backcopy, byteaddr(back, ZP), logiw*logih*4)) == 0 && !mouseredraw)
+		return;
+
+	r = Rect(0, 0, physw, physh);
+	if(front != nil && (Dx(front->r) != physw || Dy(front->r) != physh)){
+		freeimage(front);
+		front = nil;
+		free(b);
+		b = nil;
+	}
+	if(b == nil && (b = realloc(b, physw*physh*4)) == nil){
+		fprint(2, "SDL_RenderPresent: %r\n");
+		return;
+	}
+	if(forceredraw || front == nil){
+		rb = resize((u32int*)byteaddr(back, ZP), Dx(back->r), Dy(back->r), b, physw, physh);
+		if(front == nil && (front = allocimage(display, r, XRGB32, 0, DNofill)) == nil){
+			fprint(2, "SDL_RenderPresent: %r\n");
+			return;
+		}
+		if(loadimage(front, r, rb, Dx(r)*Dy(r)*4) < 0){
+			fprint(2, "SDL_RenderPresent: %r\n");
+			return;
+		}
+	}
+
+	if(cursor != nil && showcursor){
+		r.min = subpt(mouse.xy, cursor->hot);
+		r.max = addpt(r.min, cursor->i->r.max);
+		if(!forceredraw && oldcursor != nil){
+			clipr.min = subpt(oldmouse.xy, oldcursor->hot);
+			clipr.max = addpt(clipr.min, oldcursor->i->r.max);
+			combinerect(&clipr, r);
+			replclipr(screen, 0, clipr);
+		}
+	}
+	draw(screen, screen->r, front, nil, ZP);
+	if(cursor != nil && showcursor)
+		draw(screen, r, cursor->i, cursor->m, ZP);
+	mouseredraw = 0;
+	oldmouse.xy = mouse.xy;
+	oldcursor = cursor;
+
+	flushimage(display, 1);
+
+	if(forceredraw)
+		memmove(backcopy, byteaddr(back, ZP), logiw*logih*4);
+	else
+		replclipr(screen, 0, screen->r);
+	forceredraw = 0;
+}
+
+Uint32
+SDL_GetWindowFlags(SDL_Window *)
+{
+	/* FIXME is this correct? */
+	return SDL_WINDOW_INPUT_FOCUS;
+}
+
+int
+SDL_RenderSetLogicalSize(SDL_Renderer *, int w, int h)
+{
+	if(logiw != w || logih != h){
+		logiw = w;
+		logih = h;
+		forceredraw = 1;
+	}
+
+	return 0;
+}
+
+int
+SDL_GetRendererOutputSize(SDL_Renderer *, int *w, int *h)
+{
+	if(w != nil)
+		*w = logiw;
+	if(h != nil)
+		*h = logih;
+
+	return 0;
+}
+
+void
+SDL_SetWindowSize(SDL_Window *, int w, int h)
+{
+	int f, n;
+
+	if(physw != w || physh != h){
+		physw = w;
+		physh = h;
+		if((f = open("/dev/wctl", OWRITE|OCEXEC)) >= 0){
+			n = fprint(f, "resize -dx %d -dy %d", w+Borderwidth*2, h+Borderwidth*2);
+			close(f);
+			if(n > 0){
+				getwindow(display, Refnone);
+				forceredraw = 1;
+			}
+		}
+	}
+}
+
+int
+SDL_ShowSimpleMessageBox(Uint32, char *title, char *message, SDL_Window *)
+{
+	/* FIXME display a GUI window? */
+	fprint(2, "%s: %s\n", title, message);
+	return 0;
+}
+
+int
+SDL_SetWindowFullscreen(SDL_Window *, Uint32)
+{
+	/* FIXME again, ft2 does NOT check the error code, figure something out */
+	werrstr("SDL_SetWindowFullscreen: not implemented");
+	return -1;
+}
+
+void
+SDL_SetWindowGrab(SDL_Window *, SDL_bool grabbed)
+{
+	/* FIXME not sure if it's worth anything */
+	USED(grabbed);
+}
+
+void
+SDL_SetWindowPosition(SDL_Window *, int x, int y)
+{
+	int f, n;
+
+	if((f = open("/dev/wctl", OWRITE|OCEXEC)) >= 0){
+		n = fprint(f, "move -minx %d -miny %d", x, y);
+		close(f);
+		if(n > 0){
+			getwindow(display, Refnone);
+			forceredraw = 1;
+		}
+	}
+}
+
+int
+SDL_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode *mode)
+{
+	if(displayIndex != 0)
+		return -1;
+
+	mode->w = physw;
+	mode->h = physh;
+	mode->format = SDL_PIXELFORMAT_ARGB8888;
+	mode->refresh_rate = 0;
+
+	return 0;
+}
+
+void
+SDL_SetWindowTitle(SDL_Window *, char *title)
+{
+	int f;
+
+	if((f = open("/dev/label", OWRITE|OTRUNC|OCEXEC)) >= 0 || (f = open("/mnt/term/dev/label", OWRITE|OTRUNC|OCEXEC)) >= 0){
+		write(f, title, strlen(title));
+		close(f);
+	}
+}
+
+void
+SDL_DestroyTexture(SDL_Texture *t)
+{
+	freememimage(t);
+}
+
+int
+SDL_SetTextureBlendMode(SDL_Texture *, SDL_BlendMode blendMode)
+{
+	if(blendMode != SDL_BLENDMODE_NONE){
+		werrstr("SDL_SetTextureBlendMode: only SDL_BLENDMODE_NONE is supported");
+		return -1;
+	}
+
+	return 0;
+}
+
+SDL_bool
+SDL_SetHint(char *name, char *value)
+{
+	/* FIXME anyone cares about name="SDL_RENDER_SCALE_QUALITY" value="(best|nearest)"? */
+	USED(name);
+	USED(value);
+
+	return SDL_FALSE;
+}
+
+SDL_Window *
+SDL_CreateWindow(char *title, int x, int y, int w, int h, Uint32)
+{
+	SDL_SetWindowTitle(&onewin, title);
+	SDL_SetWindowSize(&onewin, w, h);
+
+	if(x != SDL_WINDOWPOS_UNDEFINED && y != SDL_WINDOWPOS_UNDEFINED){ /* FIXME either of these can be undefined */
+		if(x == SDL_WINDOWPOS_CENTERED)
+			x = display->image->r.min.x + (Dx(display->image->r) - w)/2;
+		if(y == SDL_WINDOWPOS_CENTERED)
+			y = display->image->r.min.y + (Dx(display->image->r) - w)/2;
+		SDL_SetWindowPosition(&onewin, x, y);
+	}
+
+	return &onewin;
+}
+
+SDL_Renderer *
+SDL_CreateRenderer(SDL_Window *, int, Uint32)
+{
+	SDL_RenderSetLogicalSize(nil, physw, physh);
+	return &oneren;
+}
+
+int
+SDL_SetRenderDrawBlendMode(SDL_Renderer *, SDL_BlendMode blendMode)
+{
+	if(blendMode != SDL_BLENDMODE_NONE){
+		werrstr("SDL_SetRenderDrawBlendMode: only SDL_BLENDMODE_NONE is supported");
+		return -1;
+	}
+
+	return 0;
+}
+
+char *
+SDL_GetCurrentVideoDriver(void)
+{
+	return "/dev/draw";
+}
+
+Uint32
+SDL_GetTicks(void)
+{
+	return nanosec()/1000000ULL;
+}
+
+SDL_bool
+SDL_HasClipboardText(void)
+{
+	/* most def */
+	return SDL_TRUE;
+}
+
+void
+SDL_SetThreadPriority(int)
+{
+	/* nothing to do here */
+}
+
+void
+SDL_RestoreWindow(SDL_Window *)
+{
+	/* nothing to do here */
+}
+
+void
+SDL_RaiseWindow(SDL_Window *)
+{
+	/* nothing to do here */
+}
+
+int
+SDL_SetSurfaceRLE(SDL_Surface *, int)
+{
+	/* nothing to do here */
+	return 0;
+}
+
+int
+SDL_SetSurfaceBlendMode(SDL_Surface *, SDL_BlendMode blendMode)
+{
+	if(blendMode != SDL_BLENDMODE_NONE){
+		werrstr("SDL_SetSurfaceBlendMode: only SDL_BLENDMODE_NONE is supported");
+		return -1;
+	}
+
+	return 0;
+}
+
+int
+SDL_LockSurface(SDL_Surface *)
+{
+	/* nothing to do here */
+	return 0;
+}
+
+int
+SDL_UnlockSurface(SDL_Surface *)
+{
+	/* nothing to do here */
+	return 0;
+}
+
+void
+SDL_DestroyRenderer(SDL_Renderer *)
+{
+	/* nothing to do here */
+}
+
+void
+SDL_DestroyWindow(SDL_Window *)
+{
+}
+
+SDL_bool
+SDL_HasSSE(void)
+{
+	/* it's not like we have builtins anyway */
+	return SDL_FALSE;
+}
+
+SDL_bool
+SDL_HasSSE2(void)
+{
+	/* it's not like we have builtins anyway */
+	return SDL_FALSE;
+}
+
+void
+SDL_EnableScreenSaver(void)
+{
+}
+
+int
+SDL_SaveBMP(SDL_Surface *s, const char *file)
+{
+	/* FIXME implement this */
+	USED(s, file);
+
+	return -1;
+}
--- /dev/null
+++ b/signal.h
@@ -1,0 +1,25 @@
+#ifndef _signal_h_
+#define _signal_h_
+
+#include "plan9.h"
+
+enum {
+	SA_RESETHAND = 1<<0, /* not used */
+
+	/* all are treated as SIGSEGV */
+	SIGILL = 4,
+	SIGABRT = 6,
+	SIGFPE = 8,
+	SIGSEGV = 11,
+};
+
+typedef void (*sa_handler)(int);
+
+struct sigaction {
+	sa_handler sa_handler;
+	int sa_flags;
+};
+
+void sigaction(int, struct sigaction *, struct sigaction *);
+
+#endif
--- /dev/null
+++ b/stdarg.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/stdbool.h
@@ -1,0 +1,6 @@
+#ifndef _stdbool_h_
+#define _stdbool_h_
+
+typedef enum { false, true } bool;
+
+#endif
--- /dev/null
+++ b/stddef.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/stdint.h
@@ -1,0 +1,21 @@
+#ifndef _stdint_h_
+#define _stdint_h_
+
+#include "plan9.h"
+#include <limits.h>
+
+typedef s8int int8_t;
+typedef u8int uint8_t;
+typedef s16int int16_t;
+typedef u16int uint16_t;
+typedef s32int int32_t;
+typedef u32int uint32_t;
+typedef s64int int64_t;
+typedef u64int uint64_t;
+typedef long ssize_t;
+typedef ulong size_t;
+typedef intptr ptrdiff_t;
+typedef intptr intptr_t;
+typedef uintptr uintptr_t;
+
+#endif
--- /dev/null
+++ b/stdio.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/stdlib.h
@@ -1,0 +1,8 @@
+#ifndef _stdlib_h_
+#define _stdlib_h_
+
+#include "plan9.h"
+
+#define exit(x) exits(x == 0 ? nil : "error")
+
+#endif
--- /dev/null
+++ b/string.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/strings.h
@@ -1,0 +1,7 @@
+#ifndef _strings_h_
+#define _strings_h_
+
+#define strcasecmp cistrcmp
+#define strncasecmp cistrncmp
+
+#endif
--- /dev/null
+++ b/sys/stat.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/sys/time.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/sys/types.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/time.h
@@ -1,0 +1,1 @@
+#include "plan9.h"
--- /dev/null
+++ b/unistd.c
@@ -1,0 +1,65 @@
+#include <unistd.h>
+
+#undef getenv
+#undef stat
+
+char *
+getenv9(char *s)
+{
+	static char t[1024];
+
+	snprint(t, sizeof(t), "%s", s = getenv(strcmp(s, "HOME") == 0 ? "home" : s));
+	free(s);
+
+	return t;
+}
+
+int
+stat9(char *filename, struct stat9 *buf)
+{
+	Dir *d;
+
+	if((d = dirstat(filename)) == nil)
+		return -1;
+
+	buf->st_size = d->length;
+	buf->st_mode = d->mode;
+	free(d);
+
+	return 0;
+}
+
+int
+mkdir(char *path, int perm)
+{
+	int f;
+
+	if(access(path, AEXIST) == 0){
+		werrstr("%s: already exists", path);
+		return -1;
+	}
+	if((f = create(path, OREAD, DMDIR|perm)) < 0){
+		werrstr("%s: can't create: %r", path);
+		return -1;
+	}
+	close(f);
+
+	return 0;
+}
+
+int
+rename(char *old, char *new)
+{
+	Dir d;
+
+	nulldir(&d);
+	d.name = new;
+
+	return dirwstat(old, &d) > 0 ? 0 : -1;
+}
+
+void
+usleep(unsigned us)
+{
+	nsleep((uvlong)us*1000ULL);
+}
--- /dev/null
+++ b/unistd.h
@@ -1,0 +1,47 @@
+#ifndef _unistd_h_
+#define _unistd_h_
+
+#include "plan9.h"
+
+enum {
+	S_IFMT = 0xff,
+	S_IFDIR = 1<<0,
+
+	F_OK = AEXIST,
+	R_OK = AREAD,
+	W_OK = AWRITE,
+	X_OK = AEXEC,
+
+	S_ISUID = 04000,
+	S_ISGID = 02000,
+	S_IRWXU = 00700,
+	S_IRUSR = 00400,
+	S_IWUSR = 00200,
+	S_IXUSR = 00100,
+	S_IRWXG = 00070,
+	S_IRGRP = 00040,
+	S_IWGRP = 00020,
+	S_IXGRP = 00010,
+	S_IRWXO = 00007,
+	S_IROTH = 00004,
+	S_IWOTH = 00002,
+	S_IXOTH = 00001,
+};
+
+#define getcwd getwd
+#define getenv getenv9
+#define stat stat9
+
+struct stat9 {
+	uvlong st_size;
+	int st_mode;
+};
+
+char *getenv9(char *s);
+int stat9(char *filename, struct stat9 *buf);
+int mkdir(char *path, int perm);
+int access(char *name, int mode);
+int rename(char *old, char *new);
+void usleep(unsigned us);
+
+#endif
--- /dev/null
+++ b/wchar.h
@@ -1,0 +1,1 @@
+#include "plan9.h"