static void sender_disconnect(void) { rw_wlock(&adist_remote_lock); /* * Check for a race between dropping rlock and acquiring wlock - * another thread can close connection in-between. */ if (adhost->adh_remote == NULL) { rw_unlock(&adist_remote_lock); return; } pjdlog_debug(2, "Closing connection to %s.", adhost->adh_remoteaddr); proto_close(adhost->adh_remote); mtx_lock(&adist_remote_mtx); adhost->adh_remote = NULL; adhost->adh_reset = true; adhost->adh_trail_name[0] = '\0'; adhost->adh_trail_offset = 0; mtx_unlock(&adist_remote_mtx); rw_unlock(&adist_remote_lock); pjdlog_warning("Disconnected from %s.", adhost->adh_remoteaddr); /* Move all in-flight requests back onto free list. */ mtx_lock(&adist_free_list_lock); mtx_lock(&adist_send_list_lock); TAILQ_CONCAT(&adist_free_list, &adist_send_list, adr_next); mtx_unlock(&adist_send_list_lock); mtx_lock(&adist_recv_list_lock); TAILQ_CONCAT(&adist_free_list, &adist_recv_list, adr_next); mtx_unlock(&adist_recv_list_lock); mtx_unlock(&adist_free_list_lock); }
static int tls_server(const char *lstaddr, void **ctxp) { struct proto_conn *tcp; struct tls_ctx *tlsctx; char *laddr; int error; if (strncmp(lstaddr, "tls://", 6) != 0) return (-1); tlsctx = malloc(sizeof(*tlsctx)); if (tlsctx == NULL) { pjdlog_warning("Unable to allocate memory."); return (ENOMEM); } laddr = strdup(lstaddr); if (laddr == NULL) { free(tlsctx); pjdlog_warning("Unable to allocate memory."); return (ENOMEM); } bcopy("tcp://", laddr, 6); if (proto_server(laddr, &tcp) == -1) { error = errno; free(tlsctx); free(laddr); return (error); } free(laddr); tlsctx->tls_sock = NULL; tlsctx->tls_tcp = tcp; tlsctx->tls_side = TLS_SIDE_SERVER_LISTEN; tlsctx->tls_wait_called = true; tlsctx->tls_magic = TLS_CTX_MAGIC; *ctxp = tlsctx; return (0); }
/* * Thread reads from or writes to local component and also handles DELETE and * FLUSH requests. */ static void * disk_thread(void *arg) { struct hast_resource *res = arg; struct hio *hio; ssize_t ret; bool clear_activemap, logerror; clear_activemap = true; for (;;) { pjdlog_debug(2, "disk: Taking request."); QUEUE_TAKE(disk, hio); while (clear_activemap) { unsigned char *map; size_t mapsize; /* * When first request is received, it means that primary * already received our activemap, merged it and stored * locally. We can now safely clear our activemap. */ mapsize = activemap_calc_ondisk_size(res->hr_local_mediasize - METADATA_SIZE, res->hr_extentsize, res->hr_local_sectorsize); map = calloc(1, mapsize); if (map == NULL) { pjdlog_warning("Unable to allocate memory to clear local activemap."); break; } if (pwrite(res->hr_localfd, map, mapsize, METADATA_SIZE) != (ssize_t)mapsize) { pjdlog_errno(LOG_WARNING, "Unable to store cleared activemap"); free(map); break; } free(map); clear_activemap = false; pjdlog_debug(1, "Local activemap cleared."); break; } reqlog(LOG_DEBUG, 2, -1, hio, "disk: (%p) Got request: ", hio); logerror = true; /* Handle the actual request. */ switch (hio->hio_cmd) { case HIO_READ: ret = pread(res->hr_localfd, hio->hio_data, hio->hio_length, hio->hio_offset + res->hr_localoff); if (ret < 0) hio->hio_error = errno; else if (ret != (int64_t)hio->hio_length) hio->hio_error = EIO; else hio->hio_error = 0; break; case HIO_WRITE: ret = pwrite(res->hr_localfd, hio->hio_data, hio->hio_length, hio->hio_offset + res->hr_localoff); if (ret < 0) hio->hio_error = errno; else if (ret != (int64_t)hio->hio_length) hio->hio_error = EIO; else hio->hio_error = 0; break; case HIO_DELETE: ret = g_delete(res->hr_localfd, hio->hio_offset + res->hr_localoff, hio->hio_length); if (ret < 0) hio->hio_error = errno; else hio->hio_error = 0; break; case HIO_FLUSH: if (!res->hr_localflush) { ret = -1; hio->hio_error = EOPNOTSUPP; logerror = false; break; } ret = g_flush(res->hr_localfd); if (ret < 0) { if (errno == EOPNOTSUPP) res->hr_localflush = false; hio->hio_error = errno; } else { hio->hio_error = 0; } break; default: PJDLOG_ABORT("Unexpected command (cmd=%hhu).", hio->hio_cmd); } if (logerror && hio->hio_error != 0) { reqlog(LOG_ERR, 0, hio->hio_error, hio, "Request failed: "); } pjdlog_debug(2, "disk: (%p) Moving request to the send queue.", hio); QUEUE_INSERT(send, hio); } /* NOTREACHED */ return (NULL); }
static void init_remote(struct hast_resource *res, struct nv *nvin) { uint64_t resuid; struct nv *nvout; unsigned char *map; size_t mapsize; #ifdef notyet /* Setup direction. */ if (proto_send(res->hr_remoteout, NULL, 0) == -1) pjdlog_errno(LOG_WARNING, "Unable to set connection direction"); #endif map = NULL; mapsize = 0; nvout = nv_alloc(); nv_add_int64(nvout, (int64_t)res->hr_datasize, "datasize"); nv_add_int32(nvout, (int32_t)res->hr_extentsize, "extentsize"); resuid = nv_get_uint64(nvin, "resuid"); res->hr_primary_localcnt = nv_get_uint64(nvin, "localcnt"); res->hr_primary_remotecnt = nv_get_uint64(nvin, "remotecnt"); nv_add_uint64(nvout, res->hr_secondary_localcnt, "localcnt"); nv_add_uint64(nvout, res->hr_secondary_remotecnt, "remotecnt"); mapsize = activemap_calc_ondisk_size(res->hr_local_mediasize - METADATA_SIZE, res->hr_extentsize, res->hr_local_sectorsize); map = malloc(mapsize); if (map == NULL) { pjdlog_exitx(EX_TEMPFAIL, "Unable to allocate memory (%zu bytes) for activemap.", mapsize); } /* * When we work as primary and secondary is missing we will increase * localcnt in our metadata. When secondary is connected and synced * we make localcnt be equal to remotecnt, which means nodes are more * or less in sync. * Split-brain condition is when both nodes are not able to communicate * and are both configured as primary nodes. In turn, they can both * make incompatible changes to the data and we have to detect that. * Under split-brain condition we will increase our localcnt on first * write and remote node will increase its localcnt on first write. * When we connect we can see that primary's localcnt is greater than * our remotecnt (primary was modified while we weren't watching) and * our localcnt is greater than primary's remotecnt (we were modified * while primary wasn't watching). * There are many possible combinations which are all gathered below. * Don't pay too much attention to exact numbers, the more important * is to compare them. We compare secondary's local with primary's * remote and secondary's remote with primary's local. * Note that every case where primary's localcnt is smaller than * secondary's remotecnt and where secondary's localcnt is smaller than * primary's remotecnt should be impossible in practise. We will perform * full synchronization then. Those cases are marked with an asterisk. * Regular synchronization means that only extents marked as dirty are * synchronized (regular synchronization). * * SECONDARY METADATA PRIMARY METADATA * local=3 remote=3 local=2 remote=2* ?! Full sync from secondary. * local=3 remote=3 local=2 remote=3* ?! Full sync from primary. * local=3 remote=3 local=2 remote=4* ?! Full sync from primary. * local=3 remote=3 local=3 remote=2 Primary is out-of-date, * regular sync from secondary. * local=3 remote=3 local=3 remote=3 Regular sync just in case. * local=3 remote=3 local=3 remote=4* ?! Full sync from primary. * local=3 remote=3 local=4 remote=2 Split-brain condition. * local=3 remote=3 local=4 remote=3 Secondary out-of-date, * regular sync from primary. * local=3 remote=3 local=4 remote=4* ?! Full sync from primary. */ if (res->hr_resuid == 0) { /* * Provider is used for the first time. If primary node done no * writes yet as well (we will find "virgin" argument) then * there is no need to synchronize anything. If primary node * done any writes already we have to synchronize everything. */ PJDLOG_ASSERT(res->hr_secondary_localcnt == 0); res->hr_resuid = resuid; if (metadata_write(res) < 0) exit(EX_NOINPUT); if (nv_exists(nvin, "virgin")) { free(map); map = NULL; mapsize = 0; } else { memset(map, 0xff, mapsize); } nv_add_int8(nvout, 1, "virgin"); nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc"); } else if (res->hr_resuid != resuid) { char errmsg[256]; free(map); (void)snprintf(errmsg, sizeof(errmsg), "Resource unique ID mismatch (primary=%ju, secondary=%ju).", (uintmax_t)resuid, (uintmax_t)res->hr_resuid); pjdlog_error("%s", errmsg); nv_add_string(nvout, errmsg, "errmsg"); if (hast_proto_send(res, res->hr_remotein, nvout, NULL, 0) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to send response to %s", res->hr_remoteaddr); } nv_free(nvout); exit(EX_CONFIG); } else if ( /* Is primary out-of-date? */ (res->hr_secondary_localcnt > res->hr_primary_remotecnt && res->hr_secondary_remotecnt == res->hr_primary_localcnt) || /* Are the nodes more or less in sync? */ (res->hr_secondary_localcnt == res->hr_primary_remotecnt && res->hr_secondary_remotecnt == res->hr_primary_localcnt) || /* Is secondary out-of-date? */ (res->hr_secondary_localcnt == res->hr_primary_remotecnt && res->hr_secondary_remotecnt < res->hr_primary_localcnt)) { /* * Nodes are more or less in sync or one of the nodes is * out-of-date. * It doesn't matter at this point which one, we just have to * send out local bitmap to the remote node. */ if (pread(res->hr_localfd, map, mapsize, METADATA_SIZE) != (ssize_t)mapsize) { pjdlog_exit(LOG_ERR, "Unable to read activemap"); } if (res->hr_secondary_localcnt > res->hr_primary_remotecnt && res->hr_secondary_remotecnt == res->hr_primary_localcnt) { /* Primary is out-of-date, sync from secondary. */ nv_add_uint8(nvout, HAST_SYNCSRC_SECONDARY, "syncsrc"); } else { /* * Secondary is out-of-date or counts match. * Sync from primary. */ nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc"); } } else if (res->hr_secondary_localcnt > res->hr_primary_remotecnt && res->hr_primary_localcnt > res->hr_secondary_remotecnt) { /* * Not good, we have split-brain condition. */ free(map); pjdlog_error("Split-brain detected, exiting."); nv_add_string(nvout, "Split-brain condition!", "errmsg"); if (hast_proto_send(res, res->hr_remotein, nvout, NULL, 0) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to send response to %s", res->hr_remoteaddr); } nv_free(nvout); /* Exit on split-brain. */ event_send(res, EVENT_SPLITBRAIN); exit(EX_CONFIG); } else /* if (res->hr_secondary_localcnt < res->hr_primary_remotecnt || res->hr_primary_localcnt < res->hr_secondary_remotecnt) */ { /* * This should never happen in practise, but we will perform * full synchronization. */ PJDLOG_ASSERT(res->hr_secondary_localcnt < res->hr_primary_remotecnt || res->hr_primary_localcnt < res->hr_secondary_remotecnt); mapsize = activemap_calc_ondisk_size(res->hr_local_mediasize - METADATA_SIZE, res->hr_extentsize, res->hr_local_sectorsize); memset(map, 0xff, mapsize); if (res->hr_secondary_localcnt > res->hr_primary_remotecnt) { /* In this one of five cases sync from secondary. */ nv_add_uint8(nvout, HAST_SYNCSRC_SECONDARY, "syncsrc"); } else { /* For the rest four cases sync from primary. */ nv_add_uint8(nvout, HAST_SYNCSRC_PRIMARY, "syncsrc"); } pjdlog_warning("This should never happen, asking for full synchronization (primary(local=%ju, remote=%ju), secondary(local=%ju, remote=%ju)).", (uintmax_t)res->hr_primary_localcnt, (uintmax_t)res->hr_primary_remotecnt, (uintmax_t)res->hr_secondary_localcnt, (uintmax_t)res->hr_secondary_remotecnt); } nv_add_uint32(nvout, (uint32_t)mapsize, "mapsize"); if (hast_proto_send(res, res->hr_remotein, nvout, map, mapsize) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to send activemap to %s", res->hr_remoteaddr); } if (map != NULL) free(map); nv_free(nvout); #ifdef notyet /* Setup direction. */ if (proto_recv(res->hr_remotein, NULL, 0) == -1) pjdlog_errno(LOG_WARNING, "Unable to set connection direction"); #endif }
int proto_common_send(int sock, const unsigned char *data, size_t size, int fd) { ssize_t done; size_t sendsize; int errcount = 0; PJDLOG_ASSERT(sock >= 0); if (data == NULL) { /* The caller is just trying to decide about direction. */ PJDLOG_ASSERT(size == 0); if (shutdown(sock, SHUT_RD) == -1) return (errno); return (0); } PJDLOG_ASSERT(data != NULL); PJDLOG_ASSERT(size > 0); do { sendsize = size < MAX_SEND_SIZE ? size : MAX_SEND_SIZE; done = send(sock, data, sendsize, MSG_NOSIGNAL); if (done == 0) { return (ENOTCONN); } else if (done == -1) { if (errno == EINTR) continue; if (errno == ENOBUFS) { /* * If there are no buffers we retry. * After each try we increase delay before the * next one and we give up after fifteen times. * This gives 11s of total wait time. */ if (errcount == 15) { pjdlog_warning("Getting ENOBUFS errors for 11s on send(), giving up."); } else { if (errcount == 0) pjdlog_warning("Got ENOBUFS error on send(), retrying for a bit."); errcount++; usleep(100000 * errcount); continue; } } /* * If this is blocking socket and we got EAGAIN, this * means the request timed out. Translate errno to * ETIMEDOUT, to give administrator a hint to * eventually increase timeout. */ if (errno == EAGAIN && blocking_socket(sock)) errno = ETIMEDOUT; return (errno); } data += done; size -= done; } while (size > 0); if (errcount > 0) { pjdlog_info("Data sent successfully after %d ENOBUFS error%s.", errcount, errcount == 1 ? "" : "s"); } if (fd == -1) return (0); return (proto_descriptor_send(sock, fd)); }
static int sender_connect(void) { unsigned char rnd[32], hash[32], resp[32]; struct proto_conn *conn; char welcome[8]; int16_t val; val = 1; if (proto_send(adhost->adh_conn, &val, sizeof(val)) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to send connection request to parent"); } if (proto_recv(adhost->adh_conn, &val, sizeof(val)) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to receive reply to connection request from parent"); } if (val != 0) { errno = val; pjdlog_errno(LOG_WARNING, "Unable to connect to %s", adhost->adh_remoteaddr); return (-1); } if (proto_connection_recv(adhost->adh_conn, true, &conn) < 0) { pjdlog_exit(EX_TEMPFAIL, "Unable to receive connection from parent"); } if (proto_connect_wait(conn, adcfg->adc_timeout) < 0) { pjdlog_errno(LOG_WARNING, "Unable to connect to %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Connected to %s.", adhost->adh_remoteaddr); /* Error in setting timeout is not critical, but why should it fail? */ if (proto_timeout(conn, adcfg->adc_timeout) < 0) pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); else pjdlog_debug(1, "Timeout set to %d.", adcfg->adc_timeout); /* Exchange welcome message, which includes version number. */ (void)snprintf(welcome, sizeof(welcome), "ADIST%02d", ADIST_VERSION); if (proto_send(conn, welcome, sizeof(welcome)) < 0) { pjdlog_errno(LOG_WARNING, "Unable to send welcome message to %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Welcome message sent (%s).", welcome); bzero(welcome, sizeof(welcome)); if (proto_recv(conn, welcome, sizeof(welcome)) < 0) { pjdlog_errno(LOG_WARNING, "Unable to receive welcome message from %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } if (strncmp(welcome, "ADIST", 5) != 0 || !isdigit(welcome[5]) || !isdigit(welcome[6]) || welcome[7] != '\0') { pjdlog_warning("Invalid welcome message from %s.", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Welcome message received (%s).", welcome); /* * Receiver can only reply with version number lower or equal to * the one we sent. */ adhost->adh_version = atoi(welcome + 5); if (adhost->adh_version > ADIST_VERSION) { pjdlog_warning("Invalid version number from %s (%d received, up to %d supported).", adhost->adh_remoteaddr, adhost->adh_version, ADIST_VERSION); proto_close(conn); return (-1); } pjdlog_debug(1, "Version %d negotiated with %s.", adhost->adh_version, adhost->adh_remoteaddr); if (proto_send(conn, adcfg->adc_name, sizeof(adcfg->adc_name)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to send name to %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Name (%s) sent.", adcfg->adc_name); if (proto_recv(conn, rnd, sizeof(rnd)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to receive challenge from %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Challenge received."); if (HMAC(EVP_sha256(), adhost->adh_password, (int)strlen(adhost->adh_password), rnd, (int)sizeof(rnd), hash, NULL) == NULL) { pjdlog_warning("Unable to generate response."); proto_close(conn); return (-1); } pjdlog_debug(1, "Response generated."); if (proto_send(conn, hash, sizeof(hash)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to send response to %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Response sent."); if (adist_random(rnd, sizeof(rnd)) == -1) { pjdlog_warning("Unable to generate challenge."); proto_close(conn); return (-1); } pjdlog_debug(1, "Challenge generated."); if (proto_send(conn, rnd, sizeof(rnd)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to send challenge to %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Challenge sent."); if (proto_recv(conn, resp, sizeof(resp)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to receive response from %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Response received."); if (HMAC(EVP_sha256(), adhost->adh_password, (int)strlen(adhost->adh_password), rnd, (int)sizeof(rnd), hash, NULL) == NULL) { pjdlog_warning("Unable to generate hash."); proto_close(conn); return (-1); } pjdlog_debug(1, "Hash generated."); if (memcmp(resp, hash, sizeof(hash)) != 0) { pjdlog_warning("Invalid response from %s (wrong password?).", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_info("Receiver authenticated."); if (proto_recv(conn, &adhost->adh_trail_offset, sizeof(adhost->adh_trail_offset)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to receive size of the most recent trail file from %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } adhost->adh_trail_offset = le64toh(adhost->adh_trail_offset); if (proto_recv(conn, &adhost->adh_trail_name, sizeof(adhost->adh_trail_name)) == -1) { pjdlog_errno(LOG_WARNING, "Unable to receive name of the most recent trail file from %s", adhost->adh_remoteaddr); proto_close(conn); return (-1); } pjdlog_debug(1, "Trail name (%s) and offset (%ju) received.", adhost->adh_trail_name, (uintmax_t)adhost->adh_trail_offset); rw_wlock(&adist_remote_lock); mtx_lock(&adist_remote_mtx); PJDLOG_ASSERT(adhost->adh_remote == NULL); PJDLOG_ASSERT(conn != NULL); adhost->adh_remote = conn; mtx_unlock(&adist_remote_mtx); rw_unlock(&adist_remote_lock); cv_signal(&adist_remote_cond); return (0); }
int metadata_read(struct hast_resource *res, bool openrw) { unsigned char *buf; struct ebuf *eb; struct nv *nv; ssize_t done; const char *str; int rerrno; bool opened_here; opened_here = false; rerrno = 0; /* * Is this first metadata_read() call for this resource? */ if (res->hr_localfd == -1) { if (provinfo(res, openrw) == -1) { rerrno = errno; goto fail; } opened_here = true; pjdlog_debug(1, "Obtained info about %s.", res->hr_localpath); if (openrw) { if (flock(res->hr_localfd, LOCK_EX | LOCK_NB) == -1) { rerrno = errno; if (errno == EOPNOTSUPP) { pjdlog_warning("Unable to lock %s (operation not supported), but continuing.", res->hr_localpath); } else { pjdlog_errno(LOG_ERR, "Unable to lock %s", res->hr_localpath); goto fail; } } pjdlog_debug(1, "Locked %s.", res->hr_localpath); } } eb = ebuf_alloc(METADATA_SIZE); if (eb == NULL) { rerrno = errno; pjdlog_errno(LOG_ERR, "Unable to allocate memory to read metadata"); goto fail; } if (ebuf_add_tail(eb, NULL, METADATA_SIZE) == -1) { rerrno = errno; pjdlog_errno(LOG_ERR, "Unable to allocate memory to read metadata"); ebuf_free(eb); goto fail; } buf = ebuf_data(eb, NULL); PJDLOG_ASSERT(buf != NULL); done = pread(res->hr_localfd, buf, METADATA_SIZE, 0); if (done == -1 || done != METADATA_SIZE) { rerrno = errno; pjdlog_errno(LOG_ERR, "Unable to read metadata"); ebuf_free(eb); goto fail; } nv = nv_ntoh(eb); if (nv == NULL) { rerrno = errno; pjdlog_errno(LOG_ERR, "Metadata read from %s is invalid", res->hr_localpath); ebuf_free(eb); goto fail; } str = nv_get_string(nv, "resource"); if (str != NULL && strcmp(str, res->hr_name) != 0) { pjdlog_error("Provider %s is not part of resource %s.", res->hr_localpath, res->hr_name); nv_free(nv); goto fail; } res->hr_datasize = nv_get_uint64(nv, "datasize"); res->hr_extentsize = (int)nv_get_uint32(nv, "extentsize"); res->hr_keepdirty = (int)nv_get_uint32(nv, "keepdirty"); res->hr_localoff = nv_get_uint64(nv, "offset"); res->hr_resuid = nv_get_uint64(nv, "resuid"); if (res->hr_role != HAST_ROLE_PRIMARY) { /* Secondary or init role. */ res->hr_secondary_localcnt = nv_get_uint64(nv, "localcnt"); res->hr_secondary_remotecnt = nv_get_uint64(nv, "remotecnt"); } if (res->hr_role != HAST_ROLE_SECONDARY) { /* Primary or init role. */ res->hr_primary_localcnt = nv_get_uint64(nv, "localcnt"); res->hr_primary_remotecnt = nv_get_uint64(nv, "remotecnt"); } str = nv_get_string(nv, "prevrole"); if (str != NULL) { if (strcmp(str, "primary") == 0) res->hr_previous_role = HAST_ROLE_PRIMARY; else if (strcmp(str, "secondary") == 0) res->hr_previous_role = HAST_ROLE_SECONDARY; } if (nv_error(nv) != 0) { errno = rerrno = nv_error(nv); pjdlog_errno(LOG_ERR, "Unable to read metadata from %s", res->hr_localpath); nv_free(nv); goto fail; } nv_free(nv); return (0); fail: if (opened_here) { close(res->hr_localfd); res->hr_localfd = -1; } errno = rerrno; return (-1); }