summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/fstream.h35
-rw-r--r--lib/fstream/Makemodule.am2
-rw-r--r--lib/fstream/get_line.c118
-rw-r--r--lib/fstream/internal.h1
-rw-r--r--tests/Makemodule.am9
-rw-r--r--tests/get_line.c164
-rw-r--r--tests/get_line.txt11
-rw-r--r--tests/test.h5
-rw-r--r--tests/test_tar.h5
9 files changed, 343 insertions, 7 deletions
diff --git a/include/fstream.h b/include/fstream.h
index 8693fff..07ee5a7 100644
--- a/include/fstream.h
+++ b/include/fstream.h
@@ -56,6 +56,12 @@ enum {
};
enum {
+ ISTREAM_LINE_LTRIM = 0x01,
+ ISTREAM_LINE_RTRIM = 0x02,
+ ISTREAM_LINE_SKIP_EMPTY = 0x04,
+};
+
+enum {
/**
* @brief Deflate compressor with gzip headers.
*
@@ -258,6 +264,35 @@ SQFS_INTERNAL const char *ostream_get_filename(ostream_t *strm);
SQFS_INTERNAL int ostream_printf(ostream_t *strm, const char *fmt, ...);
/**
+ * @brief Read a line of text from an input stream
+ *
+ * @memberof istream_t
+ *
+ * The line returned is allocated using malloc and must subsequently be
+ * freed when it is no longer needed. The line itself is always null-terminated
+ * and never includes the line break characters (LF or CR-LF).
+ *
+ * If the flag @ref ISTREAM_LINE_LTRIM is set, leading white space characters
+ * are removed. If the flag @ref ISTREAM_LINE_RTRIM is set, trailing white space
+ * characters are remvoed.
+ *
+ * If the flag @ref ISTREAM_LINE_SKIP_EMPTY is set and a line is discovered to
+ * be empty (after the optional trimming), the function discards the empty line
+ * and retries. The given line_num pointer is used to increment the line
+ * number.
+ *
+ * @param strm A pointer to an input stream.
+ * @param out Returns a pointer to a line on success.
+ * @param line_num This is incremented if lines are skipped.
+ * @param flags A combination of flags controling the functions behaviour.
+ *
+ * @return Zero on success, a negative value on error, a positive value if
+ * end-of-file was reached without reading any data.
+ */
+SQFS_INTERNAL int istream_get_line(istream_t *strm, char **out,
+ size_t *line_num, int flags);
+
+/**
* @brief Read data from an input stream
*
* @memberof istream_t
diff --git a/lib/fstream/Makemodule.am b/lib/fstream/Makemodule.am
index 0c24c6f..ad5f426 100644
--- a/lib/fstream/Makemodule.am
+++ b/lib/fstream/Makemodule.am
@@ -1,7 +1,7 @@
libfstream_a_SOURCES = include/fstream.h
libfstream_a_SOURCES += lib/fstream/internal.h
libfstream_a_SOURCES += lib/fstream/ostream.c lib/fstream/printf.c
-libfstream_a_SOURCES += lib/fstream/istream.c
+libfstream_a_SOURCES += lib/fstream/istream.c lib/fstream/get_line.c
libfstream_a_SOURCES += lib/fstream/compressor.c
libfstream_a_SOURCES += lib/fstream/compress/ostream_compressor.c
libfstream_a_SOURCES += lib/fstream/uncompress/istream_compressor.c
diff --git a/lib/fstream/get_line.c b/lib/fstream/get_line.c
new file mode 100644
index 0000000..f7e0b59
--- /dev/null
+++ b/lib/fstream/get_line.c
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * get_line.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "internal.h"
+
+static void ltrim(char *buffer)
+{
+ size_t i = 0;
+
+ while (isspace(buffer[i]))
+ ++i;
+
+ if (i > 0)
+ memmove(buffer, buffer + i, strlen(buffer + i) + 1);
+}
+
+static void rtrim(char *buffer)
+{
+ size_t i = strlen(buffer);
+
+ while (i > 0 && isspace(buffer[i - 1]))
+ --i;
+
+ buffer[i] = '\0';
+}
+
+static size_t trim(char *buffer, int flags)
+{
+ if (flags & ISTREAM_LINE_LTRIM)
+ ltrim(buffer);
+
+ if (flags & ISTREAM_LINE_RTRIM)
+ rtrim(buffer);
+
+ return strlen(buffer);
+}
+
+int istream_get_line(istream_t *strm, char **out,
+ size_t *line_num, int flags)
+{
+ char *line = NULL, *new;
+ size_t i, line_len = 0;
+ bool have_line = false;
+
+ *out = NULL;
+
+ for (;;) {
+ if (istream_precache(strm))
+ return -1;
+
+ if (strm->buffer_used == 0) {
+ if (line_len == 0)
+ goto out_eof;
+
+ line_len = trim(line, flags);
+
+ if (line_len == 0 &&
+ (flags & ISTREAM_LINE_SKIP_EMPTY)) {
+ goto out_eof;
+ }
+ break;
+ }
+
+ for (i = 0; i < strm->buffer_used; ++i) {
+ if (strm->buffer[i] == '\n')
+ break;
+ }
+
+ if (i < strm->buffer_used) {
+ have_line = true;
+ strm->buffer_offset = i + 1;
+
+ if (i > 0 && strm->buffer[i - 1] == '\r')
+ --i;
+ } else {
+ strm->buffer_offset = i;
+ }
+
+ new = realloc(line, line_len + i + 1);
+ if (new == NULL)
+ goto fail_errno;
+
+ line = new;
+ memcpy(line + line_len, strm->buffer, i);
+ line_len += i;
+ line[line_len] = '\0';
+
+ if (have_line) {
+ line_len = trim(line, flags);
+
+ if (line_len == 0 &&
+ (flags & ISTREAM_LINE_SKIP_EMPTY)) {
+ free(line);
+ line = NULL;
+ have_line = false;
+ *line_num += 1;
+ continue;
+ }
+ break;
+ }
+ }
+
+ *out = line;
+ return 0;
+fail_errno:
+ fprintf(stderr, "%s: " PRI_SZ ": %s.\n", strm->get_filename(strm),
+ *line_num, strerror(errno));
+ free(line);
+ *out = NULL;
+ return -1;
+out_eof:
+ free(line);
+ *out = NULL;
+ return 1;
+}
diff --git a/lib/fstream/internal.h b/lib/fstream/internal.h
index 2dc81e4..4f02f8c 100644
--- a/lib/fstream/internal.h
+++ b/lib/fstream/internal.h
@@ -16,6 +16,7 @@
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
+#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
diff --git a/tests/Makemodule.am b/tests/Makemodule.am
index 94bfd4b..58cd27e 100644
--- a/tests/Makemodule.am
+++ b/tests/Makemodule.am
@@ -230,6 +230,11 @@ fstree_fuzz_LDADD = libfstree.a libcompat.a
tar_fuzz_SOURCES = tests/tar_fuzz.c
tar_fuzz_LDADD = libtar.a libfstream.a libcompat.a
+test_get_line_SOURCES = tests/get_line.c tests/test.h
+test_get_line_LDADD = libfstream.a libcompat.a
+test_get_line_CPPFLAGS = $(AM_CPPFLAGS)
+test_get_line_CPPFLAGS += -DTESTFILE=$(top_srcdir)/tests/get_line.txt
+
check_PROGRAMS += test_mknode_simple test_mknode_slink test_mknode_reg
check_PROGRAMS += test_mknode_dir test_gen_inode_numbers test_add_by_path
check_PROGRAMS += test_get_path test_fstree_sort test_fstree_from_file
@@ -245,6 +250,7 @@ check_PROGRAMS += test_tar_sparse_gnu test_tar_sparse_gnu0 test_tar_sparse_gnu1
check_PROGRAMS += test_tar_sparse_gnu2 test_tar_sparse_gnu3
check_PROGRAMS += test_tar_xattr_bsd test_tar_xattr_schily
check_PROGRAMS += test_tar_xattr_schily_bin test_tar_target_filled
+check_PROGRAMS += test_get_line
noinst_PROGRAMS += fstree_fuzz tar_fuzz
@@ -261,7 +267,7 @@ TESTS += test_tar_pax5
TESTS += test_tar_sparse_gnu test_tar_sparse_gnu0
TESTS += test_tar_sparse_gnu1 test_tar_sparse_gnu2 test_tar_sparse_gnu3
TESTS += test_tar_xattr_bsd test_tar_xattr_schily
-TESTS += test_tar_xattr_schily_bin test_tar_target_filled
+TESTS += test_tar_xattr_schily_bin test_tar_target_filled test_get_line
if CORPORA_TESTS
check_SCRIPTS += tests/cantrbry.sh tests/test_tar_sqfs.sh tests/pack_dir_root.sh
@@ -274,3 +280,4 @@ EXTRA_DIST += $(top_srcdir)/tests/fstree1.txt
EXTRA_DIST += $(top_srcdir)/tests/corpus/cantrbry.tar.xz
EXTRA_DIST += $(top_srcdir)/tests/corpus/cantrbry.sha512
EXTRA_DIST += $(top_srcdir)/tests/pack_dir_root.txt.ref
+EXTRA_DIST += $(top_srcdir)/tests/get_line.txt
diff --git a/tests/get_line.c b/tests/get_line.c
new file mode 100644
index 0000000..c317c0e
--- /dev/null
+++ b/tests/get_line.c
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * get_line.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "fstream.h"
+#include "test.h"
+
+typedef struct {
+ size_t line_num;
+ const char *str;
+} line_t;
+
+static void run_test_case(const line_t *lines, size_t count,
+ int flags)
+{
+ size_t i, line_num, old_line_num;
+ istream_t *fp;
+ char *line;
+ int ret;
+
+ fp = istream_open_file(STRVALUE(TESTFILE));
+ TEST_NOT_NULL(fp);
+
+ line_num = 1;
+ line = NULL;
+
+ for (i = 0; i < count; ++i) {
+ old_line_num = line_num;
+ ret = istream_get_line(fp, &line, &line_num, flags);
+
+ TEST_ASSERT(line_num >= old_line_num);
+ TEST_EQUAL_I(ret, 0);
+ TEST_NOT_NULL(line);
+
+ TEST_EQUAL_UI(line_num, lines[i].line_num);
+ TEST_STR_EQUAL(line, lines[i].str);
+
+ free(line);
+ line = NULL;
+ line_num += 1;
+ }
+
+ ret = istream_get_line(fp, &line, &line_num, flags);
+ TEST_ASSERT(ret > 0);
+
+ sqfs_destroy(fp);
+}
+
+static const line_t lines_raw[] = {
+ { 1, "" },
+ { 2, "The quick" },
+ { 3, " " },
+ { 4, " brown fox " },
+ { 5, "" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 9, "" },
+ { 10, "dog" },
+ { 11, "" },
+};
+
+static const line_t lines_ltrim[] = {
+ { 1, "" },
+ { 2, "The quick" },
+ { 3, "" },
+ { 4, "brown fox " },
+ { 5, "" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 9, "" },
+ { 10, "dog" },
+ { 11, "" },
+};
+
+static const line_t lines_rtrim[] = {
+ { 1, "" },
+ { 2, "The quick" },
+ { 3, "" },
+ { 4, " brown fox" },
+ { 5, "" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 9, "" },
+ { 10, "dog" },
+ { 11, "" },
+};
+
+static const line_t lines_trim[] = {
+ { 1, "" },
+ { 2, "The quick" },
+ { 3, "" },
+ { 4, "brown fox" },
+ { 5, "" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 9, "" },
+ { 10, "dog" },
+ { 11, "" },
+};
+
+static const line_t lines_no_empty[] = {
+ { 2, "The quick" },
+ { 3, " " },
+ { 4, " brown fox " },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 10, "dog" },
+};
+
+static const line_t lines_no_empty_ltrim[] = {
+ { 2, "The quick" },
+ { 4, "brown fox " },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 10, "dog" },
+};
+
+static const line_t lines_no_empty_rtrim[] = {
+ { 2, "The quick" },
+ { 4, " brown fox" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 10, "dog" },
+};
+
+static const line_t lines_no_empty_trim[] = {
+ { 2, "The quick" },
+ { 4, "brown fox" },
+ { 6, "jumps over" },
+ { 7, "the" },
+ { 8, "lazy" },
+ { 10, "dog" },
+};
+
+int main(void)
+{
+ run_test_case(lines_raw, 11, 0);
+ run_test_case(lines_ltrim, 11, ISTREAM_LINE_LTRIM);
+ run_test_case(lines_rtrim, 11, ISTREAM_LINE_RTRIM);
+ run_test_case(lines_trim, 11,
+ ISTREAM_LINE_LTRIM | ISTREAM_LINE_RTRIM);
+
+ run_test_case(lines_no_empty, 7, ISTREAM_LINE_SKIP_EMPTY);
+ run_test_case(lines_no_empty_ltrim, 6,
+ ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM);
+ run_test_case(lines_no_empty_rtrim, 6,
+ ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_RTRIM);
+ run_test_case(lines_no_empty_trim, 6,
+ ISTREAM_LINE_SKIP_EMPTY | ISTREAM_LINE_LTRIM |
+ ISTREAM_LINE_RTRIM);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/get_line.txt b/tests/get_line.txt
new file mode 100644
index 0000000..a1994f0
--- /dev/null
+++ b/tests/get_line.txt
@@ -0,0 +1,11 @@
+
+The quick
+
+ brown fox
+
+jumps over
+the
+lazy
+
+dog
+
diff --git a/tests/test.h b/tests/test.h
index 7d38fd0..bc3ac92 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -13,6 +13,11 @@
#include <stdio.h>
#include <errno.h>
+#define STR(x) #x
+#define STRVALUE(x) STR(x)
+
+#define TEST_PATH STRVALUE(TESTPATH)
+
#if defined(__GNUC__) || defined(__clang__)
# define ATTRIB_UNUSED __attribute__ ((unused))
#else
diff --git a/tests/test_tar.h b/tests/test_tar.h
index 9ec2b12..0d5ccc1 100644
--- a/tests/test_tar.h
+++ b/tests/test_tar.h
@@ -11,9 +11,4 @@
#include "tar.h"
#include "test.h"
-#define STR(x) #x
-#define STRVALUE(x) STR(x)
-
-#define TEST_PATH STRVALUE(TESTPATH)
-
#endif /* TEST_TAR_H */