Add libString.
diff --git a/src/libString/s_rdinstack.c b/src/libString/s_rdinstack.c
new file mode 100644
index 0000000..de12d21
--- /dev/null
+++ b/src/libString/s_rdinstack.c
@@ -0,0 +1,141 @@
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "libString.h"
+
+struct Sinstack{
+	int	depth;
+	Biobuf	*fp[32];	/* hard limit to avoid infinite recursion */
+};
+
+/* initialize */
+extern Sinstack *
+s_allocinstack(char *file)
+{
+	Sinstack *sp;
+	Biobuf *fp;
+
+	fp = Bopen(file, OREAD);
+	if(fp == nil)
+		return nil;
+
+	sp = malloc(sizeof *sp);
+	sp->depth = 0;
+	sp->fp[0] = fp;
+	return sp;
+}
+
+extern void
+s_freeinstack(Sinstack *sp)
+{
+	while(sp->depth >= 0)
+		Bterm(sp->fp[sp->depth--]);
+	free(sp);
+}
+
+/*  Append an input line to a String.
+ *
+ *  Empty lines and leading whitespace are removed.
+ */
+static char *
+rdline(Biobuf *fp, String *to)
+{
+	int c;
+	int len = 0;
+
+	c = Bgetc(fp);
+
+	/* eat leading white */
+	while(c==' ' || c=='\t' || c=='\n' || c=='\r')
+		c = Bgetc(fp);
+
+	if(c < 0)
+		return 0;
+
+	for(;;){
+		switch(c) {
+		case -1:
+			goto out;
+		case '\\':
+			c = Bgetc(fp);
+			if (c != '\n') {
+				s_putc(to, '\\');
+				s_putc(to, c);
+				len += 2;
+			}
+			break;
+		case '\r':
+			break;
+		case '\n':
+			if(len != 0)
+				goto out;
+			break;
+		default:
+			s_putc(to, c);
+			len++;
+			break;
+		}
+		c = Bgetc(fp);
+	}
+out:
+	s_terminate(to);
+	return to->ptr - len;
+}
+
+/* Append an input line to a String.
+ *
+ * Returns a pointer to the character string (or 0).
+ * Leading whitespace and newlines are removed.
+ * Lines starting with #include cause us to descend into the new file.
+ * Empty lines and other lines starting with '#' are ignored.
+ */ 
+extern char *
+s_rdinstack(Sinstack *sp, String *to)
+{
+	char *p;
+	Biobuf *fp, *nfp;
+
+	s_terminate(to);
+	fp = sp->fp[sp->depth];
+
+	for(;;){
+		p = rdline(fp, to);
+		if(p == nil){
+			if(sp->depth == 0)
+				break;
+			Bterm(fp);
+			sp->depth--;
+			return s_rdinstack(sp, to);
+		}
+
+		if(strncmp(p, "#include", 8) == 0 && (p[8] == ' ' || p[8] == '\t')){
+			to->ptr = p;
+			p += 8;
+
+			/* sanity (and looping) */
+			if(sp->depth >= nelem(sp->fp))
+				sysfatal("s_recgetline: includes too deep");
+
+			/* skip white */
+			while(*p == ' ' || *p == '\t')
+				p++;
+
+			nfp = Bopen(p, OREAD);
+			if(nfp == nil)
+				continue;
+			sp->depth++;
+			sp->fp[sp->depth] = nfp;
+			return s_rdinstack(sp, to);
+		}
+
+		/* got milk? */
+		if(*p != '#')
+			break;
+
+		/* take care of comments */
+		to->ptr = p;
+		s_terminate(to);
+	}
+	return p;
+}