Support NUL-containing lines in cols(1)

This required an architectural change in getlines() by also storing
the line length.
diff --git a/README b/README
index 580caca..6152930 100644
--- a/README
+++ b/README
@@ -21,7 +21,7 @@
 0=*|x chroot          .
 0=*|o cksum           .
 0=*|o cmp             .
- #*|x cols            .
+0#*|x cols            .
  =*|o comm            .
 0=*|o cp              (-i)
 0=*|x cron            .
diff --git a/cols.c b/cols.c
index 6892070..ae7d5b2 100644
--- a/cols.c
+++ b/cols.c
@@ -24,7 +24,7 @@
 	FILE *fp;
 	struct winsize w;
 	struct linebuf b = EMPTY_LINEBUF;
-	size_t chars = 65, maxlen = 0, i, j, k, len, bytes, cols, rows;
+	size_t chars = 65, maxlen = 0, i, j, k, len, cols, rows;
 	int cflag = 0, ret = 0;
 	char *p;
 
@@ -63,10 +63,11 @@
 	}
 
 	for (i = 0; i < b.nlines; i++) {
-		len = utflen(b.lines[i]);
-		bytes = strlen(b.lines[i]);
-		if (len && bytes && b.lines[i][bytes - 1] == '\n') {
-			b.lines[i][bytes - 1] = '\0';
+		/* TODO: fix libutf to run utflen on a memory chunk
+		 * of given size to also handle embedded NULs */
+		len = utflen(b.lines[i].data);
+		if (len && b.lines[i].data[b.lines[i].len - 1] == '\n') {
+			b.lines[i].data[--(b.lines[i].len)] = '\0';
 			len--;
 		}
 		if (len > maxlen)
@@ -78,8 +79,11 @@
 
 	for (i = 0; i < rows; i++) {
 		for (j = 0; j < cols && i + j * rows < b.nlines; j++) {
-			len = utflen(b.lines[i + j * rows]);
-			fputs(b.lines[i + j * rows], stdout);
+			/* TODO: fix libutf to run utflen on a memory chunk
+			 * of given size to also handle embedded NULs */
+			len = utflen(b.lines[i + j * rows].data);
+			fwrite(b.lines[i + j * rows].data, 1,
+			       b.lines[i + j * rows].len, stdout);
 			if (j < cols - 1)
 				for (k = len; k < maxlen + 1; k++)
 					putchar(' ');
diff --git a/libutil/getlines.c b/libutil/getlines.c
index 9842d2b..4af7a25 100644
--- a/libutil/getlines.c
+++ b/libutil/getlines.c
@@ -19,12 +19,13 @@
 			b->lines = erealloc(b->lines, b->capacity * sizeof(*b->lines));
 		}
 		linelen = len;
-		b->lines[b->nlines - 1] = memcpy(emalloc(linelen + 1), line, linelen + 1);
+		b->lines[b->nlines - 1].data = memcpy(emalloc(linelen + 1), line, linelen + 1);
+		b->lines[b->nlines - 1].len = linelen;
 	}
 	free(line);
-	if (b->lines && b->nlines && linelen && b->lines[b->nlines - 1][linelen - 1] != '\n') {
-		b->lines[b->nlines - 1] = erealloc(b->lines[b->nlines - 1], linelen + 2);
-		b->lines[b->nlines - 1][linelen] = '\n';
-		b->lines[b->nlines - 1][linelen + 1] = '\0';
+	if (b->lines && b->nlines && linelen && b->lines[b->nlines - 1].data[linelen - 1] != '\n') {
+		b->lines[b->nlines - 1].data = erealloc(b->lines[b->nlines - 1].data, linelen + 2);
+		b->lines[b->nlines - 1].data[linelen] = '\n';
+		b->lines[b->nlines - 1].data[linelen + 1] = '\0';
 	}
 }
diff --git a/text.h b/text.h
index 95d2579..99a72ff 100644
--- a/text.h
+++ b/text.h
@@ -1,7 +1,12 @@
 /* See LICENSE file for copyright and license details. */
 
+struct linebufline {
+	char *data;
+	size_t len;
+};
+
 struct linebuf {
-	char **lines;
+	struct linebufline *lines;
 	size_t nlines;
 	size_t capacity;
 };