Let's see, some comments. (As usual in such cases I try to steer
clear of matters that are minor or purely lexical style choices.)
I tried compiling it, there were a couple of undeclared
variables.
Your choice of semantics is reasonable. It might be nice to get
more information on an error, but then again doing that might be
wasted effort. The choice made gives notice that an error has
occurred, and also protects against downstream problems, so it
seems like a good balance. The function level comments do a good
job of descrbing the semantics.
The comment before ucs4() says "If the buffer size is 0, the
buffer pointer may be NULL." However, this seems at odds with
the code after (and looking again now, also just before) 'error:'
in that function:
error:
if (i < sz)
buf[i] = 0;
else
buf[sz-1] = 0;
return -1;
To me this looks like if 'sz' is less than 1, or 'buf' is NULL,
then the 'else' branch acts inappropriately. I think that is
also true of the normal return path that appears just preivously.
Also, in utf8(), I'm not sure if the logic around ensuring a 0
terminating byte gets written is correct; I think it succumbs
to the same problem but I haven't checked carefully to be sure.
In any event how that works (or is meant to work) is a bit subtle
and probably merits an explanatory comment.
The high level structure of the function definitions is somewhat
dissatisfying. The code is locally simple but it seems like at
a higher level it's more complicated than it needs to be. Partly
this is just a personal dislike of what seems like more control
flow than necessary. To put this another way, it seems like it
would be good to try to simplify the overall organization,
perhaps in some cases computing the values needed by means of
expressions rather than control flow. As it is now the balance
seems weighted too heavily in the direction of local concerns
as opposed to broader considerations. At that is my impression,
fwiw.