From 19b358a447cf62da8aff1f4ef0235b04f39499eb Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Fri, 15 Jun 2018 09:26:17 -0400
Subject: Bring read/write interface closer to C standard

---
 NEWS              |  1 +
 serd/serd.h       | 44 +++++++++++++++++++++++---------------------
 src/byte_sink.h   | 18 +++++++++---------
 src/byte_source.c |  2 +-
 src/byte_source.h |  4 ++--
 src/node.c        | 16 ++++++++--------
 src/reader.c      |  4 ++--
 src/serdi.c       | 11 +++++++----
 src/uri.c         | 40 ++++++++++++++++++++--------------------
 src/writer.c      | 27 +++++++++++++--------------
 tests/serd_test.c |  2 +-
 11 files changed, 87 insertions(+), 82 deletions(-)

diff --git a/NEWS b/NEWS
index d57791bb..f7e72ea3 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,7 @@ serd (1.0.0) unstable;
   * Make serd_strtod API const-correct
   * Make nodes opaque
   * Remove half-baked serd_uri_to_path()
+  * Bring read/write interface closer to C standard
 
  -- David Robillard <d@drobilla.net>  Sat, 19 Jan 2019 13:31:12 +0100
 
diff --git a/serd/serd.h b/serd/serd.h
index 031d84c0..3698d30c 100644
--- a/serd/serd.h
+++ b/serd/serd.h
@@ -362,17 +362,29 @@ typedef int (*SerdStreamErrorFunc)(void* stream);
    @param size Size of a single element of data in bytes (always 1).
    @param nmemb Number of elements to read.
    @param stream Stream to read from (FILE* for fread).
-   @return Number of elements (bytes) read.
+   @return Number of elements (bytes) read, which is short on error.
 */
-typedef size_t (*SerdSource)(void*  buf,
-                             size_t size,
-                             size_t nmemb,
-                             void*  stream);
+typedef size_t (*SerdReadFunc)(void*  buf,
+                               size_t size,
+                               size_t nmemb,
+                               void*  stream);
 
 /**
    Sink function for raw string output.
+
+   Identical semantics to `fwrite`, but may set errno for more informative
+   error reporting than supported by SerdStreamErrorFunc.
+
+   @param buf Input buffer.
+   @param size Size of a single element of data in bytes (always 1).
+   @param nmemb Number of elements to read.
+   @param stream Stream to write to (FILE* for fread).
+   @return Number of elements (bytes) written, which is short on error.
 */
-typedef size_t (*SerdSink)(const void* buf, size_t len, void* stream);
+typedef size_t (*SerdWriteFunc)(const void* buf,
+                                size_t      size,
+                                size_t      nmemb,
+                                void*       stream);
 
 /**
    @}
@@ -424,7 +436,7 @@ serd_uri_resolve(const SerdURI* r, const SerdURI* base, SerdURI* t);
 */
 SERD_API
 size_t
-serd_uri_serialise(const SerdURI* uri, SerdSink sink, void* stream);
+serd_uri_serialise(const SerdURI* uri, SerdWriteFunc sink, void* stream);
 
 /**
    Serialise `uri` relative to `base` with a series of calls to `sink`.
@@ -438,7 +450,7 @@ size_t
 serd_uri_serialise_relative(const SerdURI* uri,
                             const SerdURI* base,
                             const SerdURI* root,
-                            SerdSink       sink,
+                            SerdWriteFunc  sink,
                             void*          stream);
 
 /**
@@ -886,7 +898,7 @@ serd_reader_read_file(SerdReader* reader,
 SERD_API
 SerdStatus
 serd_reader_start_stream(SerdReader*         reader,
-                         SerdSource          read_func,
+                         SerdReadFunc        read_func,
                          SerdStreamErrorFunc error_func,
                          void*               stream,
                          const char*         name,
@@ -958,7 +970,7 @@ serd_writer_new(SerdSyntax     syntax,
                 SerdStyle      style,
                 SerdEnv*       env,
                 const SerdURI* base_uri,
-                SerdSink       ssink,
+                SerdWriteFunc  ssink,
                 void*          stream);
 
 /**
@@ -975,16 +987,6 @@ SERD_API
 SerdEnv*
 serd_writer_get_env(SerdWriter* writer);
 
-/**
-   A convenience sink function for writing to a FILE*.
-
-   This function can be used as a SerdSink when writing to a FILE*.  The
-   `stream` parameter must be a FILE* opened for writing.
-*/
-SERD_API
-size_t
-serd_file_sink(const void* buf, size_t len, void* stream);
-
 /**
    A convenience sink function for writing to a string.
 
@@ -995,7 +997,7 @@ serd_file_sink(const void* buf, size_t len, void* stream);
 */
 SERD_API
 size_t
-serd_buffer_sink(const void* buf, size_t len, void* stream);
+serd_buffer_sink(const void* buf, size_t size, size_t nmemb, void* stream);
 
 /**
    Finish a serialisation to a buffer with serd_buffer_sink().
diff --git a/src/byte_sink.h b/src/byte_sink.h
index dca08825..cd9cbce6 100644
--- a/src/byte_sink.h
+++ b/src/byte_sink.h
@@ -23,15 +23,15 @@
 #include "serd/serd.h"
 
 typedef struct SerdByteSinkImpl {
-	SerdSink sink;
-	void*    stream;
-	char*    buf;
-	size_t   size;
-	size_t   block_size;
+	SerdWriteFunc sink;
+	void*         stream;
+	char*         buf;
+	size_t        size;
+	size_t        block_size;
 } SerdByteSink;
 
 static inline SerdByteSink
-serd_byte_sink_new(SerdSink sink, void* stream, size_t block_size)
+serd_byte_sink_new(SerdWriteFunc sink, void* stream, size_t block_size)
 {
 	SerdByteSink bsink;
 	bsink.sink       = sink;
@@ -48,7 +48,7 @@ static inline void
 serd_byte_sink_flush(SerdByteSink* bsink)
 {
 	if (bsink->block_size > 1 && bsink->size > 0) {
-		bsink->sink(bsink->buf, bsink->size, bsink->stream);
+		bsink->sink(bsink->buf, 1, bsink->size, bsink->stream);
 		bsink->size = 0;
 	}
 }
@@ -67,7 +67,7 @@ serd_byte_sink_write(const void* buf, size_t len, SerdByteSink* bsink)
 	if (len == 0) {
 		return 0;
 	} else if (bsink->block_size == 1) {
-		return bsink->sink(buf, len, bsink->stream);
+		return bsink->sink(buf, 1, len, bsink->stream);
 	}
 
 	const size_t orig_len = len;
@@ -83,7 +83,7 @@ serd_byte_sink_write(const void* buf, size_t len, SerdByteSink* bsink)
 
 		// Flush page if buffer is full
 		if (bsink->size == bsink->block_size) {
-			bsink->sink(bsink->buf, bsink->block_size, bsink->stream);
+			bsink->sink(bsink->buf, 1, bsink->block_size, bsink->stream);
 			bsink->size = 0;
 		}
 	}
diff --git a/src/byte_source.c b/src/byte_source.c
index 7cd613a0..5c38e53c 100644
--- a/src/byte_source.c
+++ b/src/byte_source.c
@@ -37,7 +37,7 @@ serd_byte_source_page(SerdByteSource* source)
 
 SerdStatus
 serd_byte_source_open_source(SerdByteSource*     source,
-                             SerdSource          read_func,
+                             SerdReadFunc        read_func,
                              SerdStreamErrorFunc error_func,
                              void*               stream,
                              const char*         name,
diff --git a/src/byte_source.h b/src/byte_source.h
index 0ef8f9a6..fc5947fa 100644
--- a/src/byte_source.h
+++ b/src/byte_source.h
@@ -30,7 +30,7 @@ typedef struct {
 } Cursor;
 
 typedef struct {
-	SerdSource          read_func;    ///< Read function (e.g. fread)
+	SerdReadFunc        read_func;    ///< Read function (e.g. fread)
 	SerdStreamErrorFunc error_func;   ///< Error function (e.g. ferror)
 	void*               stream;       ///< Stream (e.g. FILE)
 	size_t              page_size;    ///< Number of bytes to read at a time
@@ -54,7 +54,7 @@ serd_byte_source_open_string(SerdByteSource* source, const char* utf8);
 
 SerdStatus
 serd_byte_source_open_source(SerdByteSource*     source,
-                             SerdSource          read_func,
+                             SerdReadFunc        read_func,
                              SerdStreamErrorFunc error_func,
                              void*               stream,
                              const char*         name,
diff --git a/src/node.c b/src/node.c
index 8a490a45..3f2fd90f 100644
--- a/src/node.c
+++ b/src/node.c
@@ -204,12 +204,12 @@ serd_uri_string_length(const SerdURI* uri)
 }
 
 static size_t
-string_sink(const void* buf, size_t len, void* stream)
+string_sink(const void* buf, size_t size, size_t nmemb, void* stream)
 {
 	char** ptr = (char**)stream;
-	memcpy(*ptr, buf, len);
-	*ptr += len;
-	return len;
+	memcpy(*ptr, buf, size * nmemb);
+	*ptr += size * nmemb;
+	return nmemb;
 }
 
 SerdNode*
@@ -279,15 +279,15 @@ serd_node_new_file_uri(const char* path,
 	SerdBuffer buffer = { uri, uri_len };
 	for (size_t i = 0; i < path_len; ++i) {
 		if (evil && path[i] == '\\') {
-			serd_buffer_sink("/", 1, &buffer);
+			serd_buffer_sink("/", 1, 1, &buffer);
 		} else if (path[i] == '%') {
-			serd_buffer_sink("%%", 2, &buffer);
+			serd_buffer_sink("%%", 1, 2, &buffer);
 		} else if (!escape || is_uri_path_char(path[i])) {
-			serd_buffer_sink(path + i, 1, &buffer);
+			serd_buffer_sink(path + i, 1, 1, &buffer);
 		} else {
 			char escape_str[4] = { '%', 0, 0, 0 };
 			snprintf(escape_str + 1, sizeof(escape_str) - 1, "%X", path[i]);
-			serd_buffer_sink(escape_str, 3, &buffer);
+			serd_buffer_sink(escape_str, 1, 3, &buffer);
 		}
 	}
 	serd_buffer_sink_finish(&buffer);
diff --git a/src/reader.c b/src/reader.c
index 7d831bbe..63e45070 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -261,7 +261,7 @@ serd_reader_read_file(SerdReader* reader,
 	}
 
 	SerdStatus st = serd_reader_start_stream(
-		reader, (SerdSource)fread, (SerdStreamErrorFunc)ferror,
+		reader, (SerdReadFunc)fread, (SerdStreamErrorFunc)ferror,
 		fd, path, SERD_PAGE_SIZE);
 
 	if (!st) {
@@ -295,7 +295,7 @@ skip_bom(SerdReader* me)
 
 SerdStatus
 serd_reader_start_stream(SerdReader*         reader,
-                         SerdSource          read_func,
+                         SerdReadFunc        read_func,
                          SerdStreamErrorFunc error_func,
                          void*               stream,
                          const char*         name,
diff --git a/src/serdi.c b/src/serdi.c
index 692ec56c..29c78b7f 100644
--- a/src/serdi.c
+++ b/src/serdi.c
@@ -287,9 +287,12 @@ main(int argc, char** argv)
 		output_style |= SERD_STYLE_BULK;
 	}
 
-	SerdWriter* writer = serd_writer_new(
-		output_syntax, (SerdStyle)output_style,
-		env, &base_uri, serd_file_sink, out_fd);
+	SerdWriter* writer = serd_writer_new(output_syntax,
+	                                     (SerdStyle)output_style,
+	                                     env,
+	                                     &base_uri,
+	                                     (SerdWriteFunc)fwrite,
+	                                     out_fd);
 
 	SerdReader* reader = serd_reader_new(
 		input_syntax, writer, NULL,
@@ -316,7 +319,7 @@ main(int argc, char** argv)
 	} else {
 		status = serd_reader_start_stream(
 			reader,
-			bulk_read ? (SerdSource)fread : serd_file_read_byte,
+			bulk_read ? (SerdReadFunc)fread : serd_file_read_byte,
 			(SerdStreamErrorFunc)ferror,
 			in_fd,
 			in_name,
diff --git a/src/uri.c b/src/uri.c
index 138ef55c..9881f43a 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -51,18 +51,18 @@ serd_file_uri_parse(const char* uri, char** hostname)
 	for (const char* s = path; *s; ++s) {
 		if (*s == '%') {
 			if (*(s + 1) == '%') {
-				serd_buffer_sink("%", 1, &buffer);
+				serd_buffer_sink("%", 1, 1, &buffer);
 				++s;
 			} else if (is_hexdig(*(s + 1)) && is_hexdig(*(s + 2))) {
 				const char code[3] = {*(s + 1), *(s + 2), 0};
 				const char c       = (char)strtoul((const char*)code, NULL, 16);
-				serd_buffer_sink(&c, 1, &buffer);
+				serd_buffer_sink(&c, 1, 1, &buffer);
 				s += 2;
 			} else {
 				s += 2;  // Junk escape, ignore
 			}
 		} else {
-			serd_buffer_sink(s, 1, &buffer);
+			serd_buffer_sink(s, 1, 1, &buffer);
 		}
 	}
 	return serd_buffer_sink_finish(&buffer);
@@ -327,18 +327,18 @@ serd_uri_resolve(const SerdURI* r, const SerdURI* base, SerdURI* t)
 
 /** Write the path of `uri` starting at index `i` */
 static size_t
-write_path_tail(SerdSink sink, void* stream, const SerdURI* uri, size_t i)
+write_path_tail(SerdWriteFunc sink, void* stream, const SerdURI* uri, size_t i)
 {
 	size_t len = 0;
 	if (i < uri->path_base.len) {
-		len += sink(uri->path_base.buf + i, uri->path_base.len - i, stream);
+		len += sink(uri->path_base.buf + i, 1, uri->path_base.len - i, stream);
 	}
 	if (uri->path.buf) {
 		if (i < uri->path_base.len) {
-			len += sink(uri->path.buf, uri->path.len, stream);
+			len += sink(uri->path.buf, 1, uri->path.len, stream);
 		} else {
 			const size_t j = (i - uri->path_base.len);
-			len += sink(uri->path.buf + j, uri->path.len - j, stream);
+			len += sink(uri->path.buf + j, 1, uri->path.len - j, stream);
 		}
 	}
 	return len;
@@ -346,7 +346,7 @@ write_path_tail(SerdSink sink, void* stream, const SerdURI* uri, size_t i)
 
 /** Write the path of `uri` relative to the path of `base`. */
 static size_t
-write_rel_path(SerdSink       sink,
+write_rel_path(SerdWriteFunc  sink,
                void*          stream,
                const SerdURI* uri,
                const SerdURI* base)
@@ -379,11 +379,11 @@ write_rel_path(SerdSink       sink,
 	// Write up references
 	size_t len = 0;
 	for (size_t u = 0; u < up; ++u) {
-		len += sink("../", 3, stream);
+		len += sink("../", 1, 3, stream);
 	}
 
 	if (last_shared_sep == 0 && up == 0) {
-		len += sink("/", 1, stream);
+		len += sink("/", 1, 1, stream);
 	}
 
 	// Write suffix
@@ -403,7 +403,7 @@ size_t
 serd_uri_serialise_relative(const SerdURI* uri,
                             const SerdURI* base,
                             const SerdURI* root,
-                            SerdSink       sink,
+                            SerdWriteFunc  sink,
                             void*          stream)
 {
 	size_t     len = 0;
@@ -415,35 +415,35 @@ serd_uri_serialise_relative(const SerdURI* uri,
 	}
 	if (!relative || (!len && base->query.buf)) {
 		if (uri->scheme.buf) {
-			len += sink(uri->scheme.buf, uri->scheme.len, stream);
-			len += sink(":", 1, stream);
+			len += sink(uri->scheme.buf, 1, uri->scheme.len, stream);
+			len += sink(":", 1, 1, stream);
 		}
 		if (uri->authority.buf) {
-			len += sink("//", 2, stream);
-			len += sink(uri->authority.buf, uri->authority.len, stream);
+			len += sink("//", 1, 2, stream);
+			len += sink(uri->authority.buf, 1, uri->authority.len, stream);
 			if (uri->authority.buf[uri->authority.len - 1] != '/' &&
 			    serd_uri_path_starts_without_slash(uri)) {
 				// Special case: ensure path begins with a slash
 				// https://tools.ietf.org/html/rfc3986#section-3.2
-				len += sink("/", 1, stream);
+				len += sink("/", 1, 1, stream);
 			}
 		}
 		len += write_path_tail(sink, stream, uri, 0);
 	}
 	if (uri->query.buf) {
-		len += sink("?", 1, stream);
-		len += sink(uri->query.buf, uri->query.len, stream);
+		len += sink("?", 1, 1, stream);
+		len += sink(uri->query.buf, 1, uri->query.len, stream);
 	}
 	if (uri->fragment.buf) {
 		// Note uri->fragment.buf includes the leading `#'
-		len += sink(uri->fragment.buf, uri->fragment.len, stream);
+		len += sink(uri->fragment.buf, 1, uri->fragment.len, stream);
 	}
 	return len;
 }
 
 /// See http://tools.ietf.org/html/rfc3986#section-5.3
 size_t
-serd_uri_serialise(const SerdURI* uri, SerdSink sink, void* stream)
+serd_uri_serialise(const SerdURI* uri, SerdWriteFunc sink, void* stream)
 {
 	return serd_uri_serialise_relative(uri, NULL, NULL, sink, stream);
 }
diff --git a/src/writer.c b/src/writer.c
index 174d331a..13dd62a6 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -370,9 +370,11 @@ write_text(SerdWriter* writer, TextContext ctx,
 }
 
 static size_t
-uri_sink(const void* buf, size_t len, void* stream)
+uri_sink(const void* buf, size_t size, size_t nmemb, void* stream)
 {
-	return write_uri((SerdWriter*)stream, (const char*)buf, len);
+	(void)size;
+	assert(size == 1);
+	return write_uri((SerdWriter*)stream, (const char*)buf, nmemb);
 }
 
 static void
@@ -870,7 +872,7 @@ serd_writer_new(SerdSyntax     syntax,
                 SerdStyle      style,
                 SerdEnv*       env,
                 const SerdURI* base_uri,
-                SerdSink       ssink,
+                SerdWriteFunc  ssink,
                 void*          stream)
 {
 	const WriteContext context = WRITE_CONTEXT_NULL;
@@ -992,24 +994,21 @@ serd_writer_get_env(SerdWriter* writer)
 }
 
 size_t
-serd_file_sink(const void* buf, size_t len, void* stream)
+serd_buffer_sink(const void* buf, size_t size, size_t nmemb, void* stream)
 {
-	return fwrite(buf, 1, len, (FILE*)stream);
-}
+	assert(size == 1);
+	(void)size;
 
-size_t
-serd_buffer_sink(const void* buf, size_t len, void* stream)
-{
 	SerdBuffer* buffer = (SerdBuffer*)stream;
-	buffer->buf = (char*)realloc(buffer->buf, buffer->len + len);
-	memcpy((uint8_t*)buffer->buf + buffer->len, buf, len);
-	buffer->len += len;
-	return len;
+	buffer->buf = (char*)realloc(buffer->buf, buffer->len + nmemb);
+	memcpy((uint8_t*)buffer->buf + buffer->len, buf, nmemb);
+	buffer->len += nmemb;
+	return nmemb;
 }
 
 char*
 serd_buffer_sink_finish(SerdBuffer* stream)
 {
-	serd_buffer_sink("", 1, stream);
+	serd_buffer_sink("", 1, 1, stream);
 	return (char*)stream->buf;
 }
diff --git a/tests/serd_test.c b/tests/serd_test.c
index 6c47d5e3..90a01f03 100644
--- a/tests/serd_test.c
+++ b/tests/serd_test.c
@@ -419,7 +419,7 @@ main(void)
 	assert(fd);
 
 	SerdWriter* writer = serd_writer_new(
-		SERD_TURTLE, (SerdStyle)0, env, NULL, serd_file_sink, fd);
+		SERD_TURTLE, (SerdStyle)0, env, NULL, (SerdWriteFunc)fwrite, fd);
 	assert(writer);
 
 	serd_writer_chop_blank_prefix(writer, "tmp");
-- 
cgit v1.2.1