A module expanding to a 0 length string is no longer considered and error condition
If a module xlat returns < 0 update sections will return fail
ssize_t radius_axlat(char **out, REQUEST *request, char const *fmt, RADIUS_ESCAPE_STRING escape,
void *escape_ctx);
-typedef size_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char const *, char *, size_t);
+typedef ssize_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char const *, char *, size_t);
int xlat_register(char const *module, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING escape,
void *instance);
void xlat_unregister(char const *module, RAD_XLAT_FUNC func,
/*
* Xlat for %{listen:foo}
*/
-static size_t xlat_listen(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out,
- size_t outlen)
+static ssize_t xlat_listen(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out,
+ size_t outlen)
{
char const *value = NULL;
CONF_PAIR *cp;
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->listener) {
+ RWDEBUG("No listener associated with this request");
*out = '\0';
return 0;
}
cp = cf_pair_find(request->listener->cs, fmt);
if (!cp || !(value = cf_pair_value(cp))) {
+ RDEBUG("Listener does not contain config item \"%s\"", fmt);
*out = '\0';
return 0;
}
/*
* Xlat for %{config:section.subsection.attribute}
*/
-static size_t xlat_config(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_config(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
{
char const *value;
CONF_PAIR *cp;
ci = cf_reference_item(request->root->config,
request->root->config, buffer);
if (!ci || !cf_item_is_pair(ci)) {
+ REDEBUG("Config item \"%s\" does not exist", fmt);
*out = '\0';
- return 0;
+ return -1;
}
cp = cf_itemtopair(ci);
/*
* Xlat for %{client:foo}
*/
-static size_t xlat_client(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out,
- size_t outlen)
+static ssize_t xlat_client(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
{
char const *value = NULL;
CONF_PAIR *cp;
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->client) {
+ RWDEBUG("No client associated with this request");
*out = '\0';
return 0;
}
strlcpy(out, request->client->shortname, outlen);
return strlen(out);
}
-
+ RDEBUG("Client does not contain config item \"%s\"", fmt);
*out = '\0';
return 0;
}
#include <stdarg.h>
typedef size_t (*RADIUS_ESCAPE_STRING)(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
-typedef size_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char const *, char *, size_t);
+typedef ssize_t (*RAD_XLAT_FUNC)(void *instance, REQUEST *, char const *, char *, size_t);
int xlat_register(char const *module, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING escape,
void *instance);
void module_failure_msg(UNUSED REQUEST *request, UNUSED char const *fmt, ...);
return waitpid(pid, status, 0);
}
-static size_t xlat_test(UNUSED void *instance, UNUSED REQUEST *request,
- UNUSED char const *fmt, UNUSED char *out, UNUSED size_t outlen)
+static ssize_t xlat_test(UNUSED void *instance, UNUSED REQUEST *request,
+ UNUSED char const *fmt, UNUSED char *out, UNUSED size_t outlen)
{
return 0;
}
/*
* Xlat for %{home_server:foo}
*/
-static size_t xlat_home_server(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_home_server(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->home_server) {
+ RWDEBUG("No home_server associated with this request");
+
*out = '\0';
return 0;
}
/*
* Xlat for %{home_server_pool:foo}
*/
-static size_t xlat_server_pool(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_server_pool(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
if (!fmt || !out || (outlen < 1)) return 0;
if (!request || !request->home_pool) {
+ RWDEBUG("No home_pool associated with this request");
+
*out = '\0';
return 0;
}
/** Print data as integer, not as VALUE.
*
*/
-static size_t xlat_integer(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
VALUE_PAIR *vp;
break;
}
+ REDEBUG("Type \"%s\" cannot be converted to integer",
+ fr_int2str(dict_attr_types, vp->da->type, PW_TYPE_INVALID));
*out = '\0';
- return 0;
+
+ return -1;
}
/** Print data as hex, not as VALUE.
*
*/
-static size_t xlat_hex(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_hex(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
size_t i;
VALUE_PAIR *vp;
if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) {
*out = '\0';
- return 0;
+ return -1;
}
ret = rad_vp2data(vp, buffer, sizeof(buffer));
/** Print data as base64, not as VALUE
*
*/
-static size_t xlat_base64(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_base64(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
VALUE_PAIR *vp;
uint8_t buffer[MAX_STRING_LEN];
ret = rad_vp2data(vp, buffer, sizeof(buffer));
if (ret < 0) {
- *out = 0;
- return 0;
+ *out = '\0';
+ return ret;
}
return fr_base64_encode(buffer, (size_t) ret, out, outlen);
/** Prints the current module processing the request
*
*/
-static size_t xlat_module(UNUSED void *instance, REQUEST *request,
- UNUSED char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_module(UNUSED void *instance, REQUEST *request,
+ UNUSED char const *fmt, char *out, size_t outlen)
{
strlcpy(out, request->module, outlen);
*
* @see modcall()
*/
-static size_t xlat_foreach(void *instance, REQUEST *request,
- UNUSED char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_foreach(void *instance, REQUEST *request,
+ UNUSED char const *fmt, char *out, size_t outlen)
{
VALUE_PAIR **pvp;
* be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
* expand to "\n\n\n"
*/
-static size_t xlat_string(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_string(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
int len;
VALUE_PAIR *vp;
/** xlat expand string attribute value
*
*/
-static size_t xlat_xlat(UNUSED void *instance, REQUEST *request,
+static ssize_t xlat_xlat(UNUSED void *instance, REQUEST *request,
char const *fmt, char *out, size_t outlen)
{
VALUE_PAIR *vp;
*
* Example %{debug:3}
*/
-static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t xlat_debug(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
int level = 0;
static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * const node,
RADIUS_ESCAPE_STRING escape, void *escape_ctx, int lvl)
{
- size_t rcode;
+ ssize_t rcode;
char *str = NULL, *child;
REQUEST *ref;
XLAT_DEBUG("%.*sexpand mod %s --> '%s'", lvl, xlat_spaces, node->fmt, child);
str = talloc_array(ctx, char, 1024); /* FIXME: have the module call talloc_asprintf */
-
+ *str = '\0'; /* Be sure the string is NULL terminated, we now only free on error */
+
rcode = node->xlat->func(node->xlat->instance, request, child, str, 1024);
talloc_free(child);
- if (rcode == 0) {
+ if (rcode < 0) {
talloc_free(str);
return NULL;
}
/*
* Allow single attribute values to be retrieved from the dhcp.
*/
-static size_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request,
- char const *fmt, char *out, size_t freespace)
+static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t freespace)
{
vp_cursor_t cursor;
VALUE_PAIR *vp, *head = NULL;
if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) {
*out = '\0';
-
return 0;
}
if ((fr_dhcp_decode_options(request->packet,
- vp->vp_octets, vp->length, &head) < 0) ||
- (!head)) {
+ vp->vp_octets, vp->length, &head) < 0) || (!head)) {
RWDEBUG("DHCP option decoding failed");
- goto fail;
+ *out = '\0';
+ return -1;
}
/* Free any unmoved pairs */
pairfree(&head);
- fail:
-
snprintf(out, freespace, "%i", decoded);
return strlen(out);
/*
* Allow single attribute values to be retrieved from the cache.
*/
-static size_t cache_xlat(void *instance, REQUEST *request,
- char const *fmt, char *out, size_t freespace)
+static ssize_t cache_xlat(void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t freespace)
{
rlm_cache_entry_t *c;
rlm_cache_t *inst = instance;
if (!c) {
RDEBUG("No cache entry for key \"%s\"", fmt);
+ *out = '\0';
goto done;
}
case PAIR_LIST_UNKNOWN:
PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
REDEBUG("Unknown list qualifier in \"%s\"", fmt);
- return 0;
+ return -1;
default:
PTHREAD_MUTEX_UNLOCK(&inst->cache_mutex);
REDEBUG("Unsupported list \"%s\"",
- fr_int2str(pair_lists, list, "¿Unknown?"));
- return 0;
+ fr_int2str(pair_lists, list, "¿Unknown?"));
+ return -1;
}
vp = pairfind(vps, target->attr, target->vendor, TAG_ANY);
if (!vp) {
RDEBUG("No instance of this attribute has been cached");
+ *out = '\0';
goto done;
}
/*
* Do xlat of strings.
*/
-static size_t exec_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
+static ssize_t exec_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
{
int result;
rlm_exec_t *inst = instance;
if (!inst->wait) {
REDEBUG("'wait' must be enabled to use exec xlat");
- out[0] = '\0';
- return 0;
+ *out = '\0';
+ return -1;
}
if (inst->input_list) {
input_pairs = radius_list(request, inst->input_list);
if (!input_pairs) {
REDEBUG("Failed to find input pairs for xlat");
- out[0] = '\0';
- return 0;
+ *out = '\0';
+ return -1;
}
}
out, outlen, input_pairs ? *input_pairs : NULL, NULL);
if (result != 0) {
out[0] = '\0';
- return 0;
+ return -1;
}
for (p = out; *p != '\0'; p++) {
/*
* Do xlat of strings!
*/
-static size_t expr_xlat(UNUSED void *instance, REQUEST *request, char const *fmt,
- char *out, size_t outlen)
+static ssize_t expr_xlat(UNUSED void *instance, REQUEST *request, char const *fmt,
+ char *out, size_t outlen)
{
int rcode;
int64_t result;
p = fmt;
rcode = get_number(request, &p, &result);
if (rcode < 0) {
- return 0;
+ return -1;
}
/*
*/
if (*p != '\0') {
RDEBUG2("Failed at %s", p);
- return 0;
+ return -1;
}
snprintf(out, outlen, "%ld", (long int) result);
* @brief Generate a random integer value
*
*/
-static size_t rand_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt,
- char *out, size_t outlen)
+static ssize_t rand_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt,
+ char *out, size_t outlen)
{
int64_t result;
/*
* Too small or too big.
*/
- if (result <= 0) return 0;
+ if (result <= 0) {
+ *out = '\0';
+ return -1;
+ }
if (result >= (1 << 30)) result = (1 << 30);
result *= fr_rand(); /* 0..2^32-1 */
* Build strings of random chars, useful for generating tokens and passcodes
* Format similar to String::Random.
*/
-static size_t randstr_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t randstr_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
char const *p;
unsigned int result;
if (outlen <= 1) return 0;
+ *out = '\0';
+
p = fmt;
while (*p && (--freespace > 0)) {
result = fr_rand();
* non printable chars).
*/
case 'h':
- if (freespace < 2)
+ if (freespace < 2) {
break;
+ }
snprintf(out, 3, "%02x", result % 256);
break;
default:
- ERROR("rlm_expr: invalid character class '%c'",
- *p);
+ ERROR("rlm_expr: invalid character class '%c'", *p);
- return 0;
+ return -1;
}
p++;
*
* Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
*/
-static size_t urlquote_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t urlquote_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
char const *p;
size_t freespace = outlen;
*
* @verbatim Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60=/img=62" @endverbatim
*/
-static size_t escape_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t escape_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
rlm_expr_t *inst = instance;
char const *p;
*
* Probably only works for ASCII
*/
-static size_t lc_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t lc_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
char *q;
char const *p;
*
* Probably only works for ASCII
*/
-static size_t uc_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t uc_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
char *q;
char const *p;
*
* Example: "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8"
*/
-static size_t md5_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t md5_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
uint8_t digest[16];
int i, len;
*
* Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
*/
-static size_t sha1_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t sha1_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
uint8_t digest[20];
int i, len;
*
* Example: "%{tobase64:foo}" == "Zm9v"
*/
-static size_t base64_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t base64_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
ssize_t len;
if ((len < 0) || ((FR_BASE64_ENC_LENGTH(len) + 1) > (ssize_t) outlen)) {
REDEBUG("xlat failed.");
*out = '\0';
- return 0;
+ return -1;
}
- return fr_base64_encode((const uint8_t *) fmt, len, out, outlen);
+ return fr_base64_encode((const uint8_t *) fmt, len, out, outlen);
}
/**
*
* Example: "%{base64tohex:Zm9v}" == "666f6f"
*/
-static size_t base64_to_hex_xlat(UNUSED void *instance, UNUSED REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t base64_to_hex_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
uint8_t decbuf[1024], *p;
declen = fr_base64_decode(fmt, len, decbuf, sizeof(decbuf));
if (declen < 0) {
REDEBUG("Base64 string invalid");
- return 0;
+ return -1;
}
p = decbuf;
{ NULL, -1, 0, NULL, NULL }
};
-static size_t xlat_idna(void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t freespace)
+static ssize_t xlat_idna(void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t freespace)
{
rlm_idn_t *inst = instance;
char *idna = NULL;
free (idna); /* Docs unclear, be safe. */
}
- RERROR("%s", idna_strerror(res));
- return 0;
+ REDEBUG("%s", idna_strerror(res));
+ return -1;
}
len = strlen(idna);
/* 253 is max DNS length */
if (!((len < (freespace - 1)) && (len <= 253))) {
/* Never provide a truncated result, as it may be queried. */
- RERROR("Conversion was truncated");
+ REDEBUG("Conversion was truncated");
free(idna);
- return 0;
+ return -1;
}
/** Expand an LDAP URL into a query, and return a string result from that query.
*
*/
-static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
- char *out, size_t freespace)
+static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
{
ldap_rcode_t status;
- size_t length = 0;
+ size_t len = 0;
ldap_instance_t *inst = instance;
LDAPURLDesc *ldap_url;
LDAPMessage *result = NULL;
if (!ldap_is_ldap_url(url)) {
REDEBUG("String passed does not look like an LDAP URL");
- return 0;
+ return -1;
}
if (ldap_url_parse(url, &ldap_url)){
REDEBUG("Parsing LDAP URL failed");
- return 0;
+ return -1;
}
/*
if (!entry) {
ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
+ len = -1;
goto free_result;
}
goto free_result;
}
- length = strlen(vals[0]);
- if (length >= freespace){
-
+ len = strlen(vals[0]);
+ if (len >= freespace){
goto free_vals;
}
free_urldesc:
ldap_free_urldesc(ldap_url);
- return length;
+ return len;
}
/** Perform LDAP-Group comparison checking
* Pulls NT-Response, LM-Response, or Challenge from MSCHAP
* attributes.
*/
-static size_t mschap_xlat(void *instance, REQUEST *request,
- char const *fmt, char *out, size_t outlen)
+static ssize_t mschap_xlat(void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
{
size_t i, data_len;
uint8_t const *data = NULL;
/*
* The xlat function
*/
-static size_t perl_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
+static ssize_t perl_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
{
rlm_perl_t *inst= (rlm_perl_t *) instance;
return dissocket;
}
-static size_t redis_xlat(void *instance, REQUEST *request,
- char const *fmt, char *out, size_t freespace)
+static ssize_t redis_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
{
REDIS_INST *inst = instance;
REDISSOCK *dissocket;
ERROR("rlm_redis (%s): redis_get_socket() failed",
inst->xlat_name);
- return 0;
+ return -1;
}
/* Query failed for some reason, release socket and return */
if ((ret >= freespace) || (!buffer_ptr)) {
RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n",
inst->xlat_name);
- ret = 0;
+ ret = -1;
goto release;
}
/*
* Not sure how to make this useful yet...
*/
-static size_t soh_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) {
+static ssize_t soh_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) {
VALUE_PAIR* vp[6];
char const *osname;
* for inserts, updates and deletes the number of rows afftected will be
* returned instead.
*/
-static size_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
+static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
{
rlm_sql_handle_t *handle = NULL;
rlm_sql_row_t row;
rlm_sql_t *inst = instance;
- size_t ret = 0;
+ ssize_t ret = 0;
+ size_t len = 0;
/*
* Add SQL-User-Name attribute just in case it is needed
*/
snprintf(buffer, sizeof(buffer), "%d", numaffected);
- ret = strlen(buffer);
- if (ret >= freespace){
+ len = strlen(buffer);
+ if (len >= freespace){
RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->config->xlat_name);
(inst->module->sql_finish_query)(handle, inst->config);
- ret = 0;
+ ret = -1;
goto finish;
}
- memcpy(out, buffer, ret + 1); /* we did bounds checking above */
-
+ memcpy(out, buffer, len + 1); /* we did bounds checking above */
+ ret = len;
+
(inst->module->sql_finish_query)(handle, inst->config);
goto finish;
} /* else it's a SELECT statement */
if (rlm_sql_select_query(&handle, inst, query)){
+ ret = -1;
+
goto finish;
}
if (ret) {
RDEBUG("SQL query failed");
(inst->module->sql_finish_select_query)(handle, inst->config);
+ ret = -1;
goto finish;
}
goto finish;
}
- ret = strlen(row[0]);
- if (ret >= freespace){
+ len = strlen(row[0]);
+ if (len >= freespace){
RDEBUG("Insufficient string space");
(inst->module->sql_finish_select_query)(handle, inst->config);
- ret = 0;
+ ret = -1;
goto finish;
}
strlcpy(out, row[0], freespace);
+ ret = len;
RDEBUG("sql_xlat finished");
*
* Example: "%{modhextohex:vvrbuctetdhc}" == "ffc1e0d3d260"
*/
-static size_t modhex_to_hex_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
+static ssize_t modhex_to_hex_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen)
{
ssize_t len;
*out = '\0';
REDEBUG("Modhex string invalid");
- return 0;
+ return -1;
}
return len;