/*
* Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
*/
/*
* BSD 3 Clause License
*
* Copyright (c) 2007, The Storage Networking Industry Association.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* - Neither the name of The Storage Networking Industry Association (SNIA)
* nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <locale.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <door.h>
#include <thread.h>
#include <ndmpd_door.h>
#include <libndmp.h>
static int ndmp_num_conn; /* number of connections */
static int ndmp_door_fildes = -1;
static char *door_buf = NULL;
static int door_buf_size = 0;
static ndmp_door_ctx_t *dec_ctx;
static ndmp_door_ctx_t *enc_ctx;
static door_arg_t arg;
static mutex_t ndmp_lock = DEFAULTMUTEX;
static int ndmp_door_setup(int opcode);
static int ndmp_door_call(ndmp_door_err_t *);
static int ndmp_door_fini(void);
static int ndmp_num_dev = 0; /* number of devices */
/* ndmp library APIs */
int
ndmp_get_devinfo(ndmp_devinfo_t **dipp, size_t *sizep)
{
ndmp_devinfo_t *dip;
int i;
ndmp_door_err_t derr;
(void) mutex_lock(&ndmp_lock);
/*
* the first door call is to get the device count
* which will be used to calculate the door buffer size
*/
if (ndmp_door_setup(NDMP_GET_DEV_CNT) ||
ndmp_door_call(&derr)) {
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
/* number of devices */
ndmp_num_dev = ndmp_door_get_int32(dec_ctx);
if (ndmp_door_fini() ||
ndmp_door_setup(NDMP_DEVICES_GET_INFO) ||
ndmp_door_call(&derr)) {
ndmp_num_dev = 0;
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
/* get the number of devices available */
*sizep = ndmp_door_get_uint32(dec_ctx);
/* calloc to ensure NULL pointers in allocated struct */
*dipp = calloc(*sizep, sizeof (ndmp_devinfo_t));
if (*dipp == NULL) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_MEM_ALLOC;
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
dip = *dipp;
for (i = 0; i < *sizep; i++, dip++) {
dip->nd_dev_type = ndmp_door_get_int32(dec_ctx);
dip->nd_name = ndmp_door_get_string(dec_ctx);
dip->nd_lun = ndmp_door_get_int32(dec_ctx);
dip->nd_sid = ndmp_door_get_int32(dec_ctx);
dip->nd_vendor = ndmp_door_get_string(dec_ctx);
dip->nd_product = ndmp_door_get_string(dec_ctx);
dip->nd_revision = ndmp_door_get_string(dec_ctx);
dip->nd_serial = ndmp_door_get_string(dec_ctx);
dip->nd_wwn = ndmp_door_get_string(dec_ctx);
if (dip->nd_name == NULL ||
dip->nd_vendor == NULL ||
dip->nd_product == NULL ||
dip->nd_revision == NULL ||
dip->nd_serial == NULL ||
dip->nd_wwn == NULL) {
/* Free all allocations if any failed */
ndmp_get_devinfo_free(dipp, sizep);
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
}
if (ndmp_door_fini()) {
/* Free all allocations if anything failed */
ndmp_get_devinfo_free(dipp, sizep);
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
(void) mutex_unlock(&ndmp_lock);
return (0);
}
void
ndmp_get_devinfo_free(ndmp_devinfo_t **dipp, size_t *sizep)
{
ndmp_devinfo_t *dip;
int i;
if (*dipp != NULL) {
dip = *dipp;
for (i = 0; i < *sizep; i++, dip++) {
free(dip->nd_name);
free(dip->nd_vendor);
free(dip->nd_product);
free(dip->nd_revision);
free(dip->nd_serial);
free(dip->nd_wwn);
}
free(*dipp);
*dipp = NULL;
}
}
int
ndmp_terminate_session(int session)
{
int ret;
int opcode = NDMP_TERMINATE_SESSION_ID;
ndmp_door_err_t derr;
(void) mutex_lock(&ndmp_lock);
if (ndmp_door_setup(opcode))
goto err;
ndmp_door_put_uint32(enc_ctx, session);
if (ndmp_door_call(&derr))
goto err;
ret = ndmp_door_get_uint32(dec_ctx);
if (ndmp_door_fini())
goto err;
(void) mutex_unlock(&ndmp_lock);
return (ret);
err:
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
int
ndmp_get_session_info(ndmp_session_info_t **sinfopp, size_t *sizep)
{
int status;
int i, j;
int prev_num_conn = 0;
int attempts = 0;
ndmp_session_info_t *sp;
ndmp_dt_pval_t *ep;
ndmp_dt_name_t *np;
ndmp_dt_name_v3_t *npv3;
ndmp_door_err_t derr;
(void) mutex_lock(&ndmp_lock);
for (attempts = 0; attempts < MAX_ALLOWED_DOOR_RETRY; attempts++) {
if (ndmp_door_setup(NDMP_GET_NUM_CONN))
goto err;
if (ndmp_door_call(&derr))
goto err;
/* number of sessions */
prev_num_conn = ndmp_num_conn;
ndmp_num_conn = ndmp_door_get_int32(dec_ctx);
if (ndmp_door_fini())
goto err;
/*
* Now setup the door again, this time
* with the intention of getting the session
* specific info.
*/
if (ndmp_door_setup(NDMP_SHOW))
goto err;
if (ndmp_door_call(&derr)) {
/*
* If we failed because of space constraints
* then reevaluate and give it another try,
* but only if the number of sessions actually changed
* between our previous call and now. If they are
* equal, then retrying is fruitless.
*/
if ((derr.door_extended_err == ENOSPC) &&
(prev_num_conn < ndmp_num_conn)) {
continue;
} else {
goto err;
}
} else {
/*
* If the door call was successful then
* no need to continue to retry. Break out
* of the loop now.
*/
break;
}
}
/* number of sessions */
*sizep = ndmp_door_get_int32(dec_ctx);
/* calloc to ensure NULL pointers in allocated struct */
*sinfopp = calloc(*sizep, sizeof (ndmp_session_info_t));
if (*sinfopp == NULL) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_MEM_ALLOC;
goto err;
}
sp = *sinfopp;
for (i = 0; i < *sizep; i++, sp++) {
status = ndmp_door_get_int32(dec_ctx);
if (status == NDMP_SESSION_NODATA)
continue;
/* connection common info */
sp->nsi_sid = ndmp_door_get_int32(dec_ctx);
sp->nsi_pver = ndmp_door_get_int32(dec_ctx);
sp->nsi_auth = ndmp_door_get_int32(dec_ctx);
sp->nsi_eof = ndmp_door_get_int32(dec_ctx);
sp->nsi_cl_addr = ndmp_door_get_string(dec_ctx);
/*
* scsi and tape data are same for all version,
* so keep reading
*/
/* connection common scsi info. */
sp->nsi_scsi.ns_scsi_open = ndmp_door_get_int32(dec_ctx);
sp->nsi_scsi.ns_adapter_name = ndmp_door_get_string(dec_ctx);
sp->nsi_scsi.ns_valid_target_set = ndmp_door_get_int32(dec_ctx);
if (sp->nsi_scsi.ns_valid_target_set) {
sp->nsi_scsi.ns_scsi_id = ndmp_door_get_int32(dec_ctx);
sp->nsi_scsi.ns_lun = ndmp_door_get_int32(dec_ctx);
}
/* connection common tape info. */
sp->nsi_tape.nt_fd = ndmp_door_get_int32(dec_ctx);
if (sp->nsi_tape.nt_fd != -1) {
sp->nsi_tape.nt_rec_count =
ndmp_door_get_uint64(dec_ctx);
sp->nsi_tape.nt_mode = ndmp_door_get_int32(dec_ctx);
sp->nsi_tape.nt_dev_name =
ndmp_door_get_string(dec_ctx);
sp->nsi_tape.nt_sid = ndmp_door_get_int32(dec_ctx);
sp->nsi_tape.nt_lun = ndmp_door_get_int32(dec_ctx);
}
/* all the V2 mover data are same as V3/V4 */
sp->nsi_mover.nm_state = ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_mode = ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_pause_reason = ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_halt_reason = ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_rec_size = ndmp_door_get_uint64(dec_ctx);
sp->nsi_mover.nm_rec_num = ndmp_door_get_uint64(dec_ctx);
sp->nsi_mover.nm_mov_pos = ndmp_door_get_uint64(dec_ctx);
sp->nsi_mover.nm_window_offset = ndmp_door_get_uint64(dec_ctx);
sp->nsi_mover.nm_window_length = ndmp_door_get_uint64(dec_ctx);
sp->nsi_mover.nm_sock = ndmp_door_get_int32(dec_ctx);
/* Read V3/V4 mover info */
if ((sp->nsi_pver == NDMP_V3) || (sp->nsi_pver == NDMP_V4)) {
sp->nsi_mover.nm_listen_sock =
ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_addr_type =
ndmp_door_get_int32(dec_ctx);
sp->nsi_mover.nm_tcp_addr =
ndmp_door_get_string(dec_ctx);
}
/* connection common data info */
sp->nsi_data.nd_oper = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_state = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_halt_reason = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_sock = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_addr_type = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_abort = ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_read_offset = ndmp_door_get_uint64(dec_ctx);
sp->nsi_data.nd_read_length = ndmp_door_get_uint64(dec_ctx);
sp->nsi_data.nd_total_size = ndmp_door_get_uint64(dec_ctx);
sp->nsi_data.nd_env_len = ndmp_door_get_uint64(dec_ctx);
/* calloc to ensure NULL pointers in allocated struct */
sp->nsi_data.nd_env =
calloc(sp->nsi_data.nd_env_len, sizeof (ndmp_dt_pval_t));
if (!sp->nsi_data.nd_env) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_MEM_ALLOC;
ndmp_get_session_info_free(sinfopp, sizep);
goto err;
}
ep = sp->nsi_data.nd_env;
for (j = 0; j < sp->nsi_data.nd_env_len; j++, ep++) {
ep->np_name = ndmp_door_get_string(dec_ctx);
ep->np_value = ndmp_door_get_string(dec_ctx);
}
sp->nsi_data.nd_tcp_addr = ndmp_door_get_string(dec_ctx);
/* Read V2 data info */
if (sp->nsi_pver == NDMP_V2) {
sp->nsi_data.nld_nlist_len =
ndmp_door_get_int64(dec_ctx);
/* calloc to ensure NULL pointers in allocated struct */
sp->nsi_data.nd_nlist.nld_nlist =
calloc(sp->nsi_data.nld_nlist_len,
sizeof (ndmp_dt_name_t));
if (!sp->nsi_data.nd_nlist.nld_nlist) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_MEM_ALLOC;
ndmp_get_session_info_free(sinfopp, sizep);
goto err;
}
np = sp->nsi_data.nd_nlist.nld_nlist;
for (j = 0; j < sp->nsi_data.nld_nlist_len; j++, np++) {
np->nn_name = ndmp_door_get_string(dec_ctx);
np->nn_dest = ndmp_door_get_string(dec_ctx);
}
} else if ((sp->nsi_pver == NDMP_V3) ||
(sp->nsi_pver == NDMP_V4)) {
/* Read V3/V4 data info */
sp->nsi_data.nd_nlist.nld_dt_v3.dv3_listen_sock =
ndmp_door_get_int32(dec_ctx);
sp->nsi_data.nd_nlist.nld_dt_v3.dv3_bytes_processed =
ndmp_door_get_uint64(dec_ctx);
sp->nsi_data.nld_nlist_len =
ndmp_door_get_uint64(dec_ctx);
/* calloc to ensure NULL pointers in allocated struct */
sp->nsi_data.nd_nlist.nld_dt_v3.dv3_nlist =
calloc(sp->nsi_data.nld_nlist_len,
sizeof (ndmp_dt_name_v3_t));
if (!sp->nsi_data.nd_nlist.nld_dt_v3.dv3_nlist) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_MEM_ALLOC;
ndmp_get_session_info_free(sinfopp, sizep);
goto err;
}
npv3 = sp->nsi_data.nd_nlist.nld_dt_v3.dv3_nlist;
for (j = 0; j < sp->nsi_data.nld_nlist_len;
j++, npv3++) {
npv3->nn3_opath = ndmp_door_get_string(dec_ctx);
npv3->nn3_dpath = ndmp_door_get_string(dec_ctx);
npv3->nn3_node = ndmp_door_get_uint64(dec_ctx);
npv3->nn3_fh_info =
ndmp_door_get_uint64(dec_ctx);
}
}
}
if (ndmp_door_fini()) {
ndmp_get_session_info_free(sinfopp, sizep);
goto err;
}
(void) mutex_unlock(&ndmp_lock);
return (0);
err:
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
void
ndmp_get_session_info_free(ndmp_session_info_t **sinfopp, size_t *sizep)
{
ndmp_session_info_t *sp;
ndmp_dt_pval_t *ep;
ndmp_dt_name_t *np;
ndmp_dt_name_v3_t *npv3;
int i, j;
/*
* Protect against dereferencing a null pointer in any of the following code.
* If *sinfopp == NULL, assume that everything was already freed previously.
*/
if (*sinfopp == NULL) {
return;
}
sp = *sinfopp;
for (i = 0; i < *sizep; i++, sp++) {
free(sp->nsi_cl_addr);
free(sp->nsi_scsi.ns_adapter_name);
if (sp->nsi_tape.nt_fd != -1) {
free(sp->nsi_tape.nt_dev_name);
}
if ((sp->nsi_pver == NDMP_V3) || (sp->nsi_pver == NDMP_V4))
free(sp->nsi_mover.nm_tcp_addr);
ep = sp->nsi_data.nd_env;
for (j = 0; j < sp->nsi_data.nd_env_len; j++, ep++) {
free(ep->np_name);
free(ep->np_value);
}
free(sp->nsi_data.nd_env);
free(sp->nsi_data.nd_tcp_addr);
if (sp->nsi_pver == NDMP_V2) {
np = sp->nsi_data.nd_nlist.nld_nlist;
for (j = 0; j < sp->nsi_data.nld_nlist_len; j++, np++) {
free(np->nn_name);
free(np->nn_dest);
}
free(sp->nsi_data.nd_nlist.nld_nlist);
} else if ((sp->nsi_pver == NDMP_V3) ||
(sp->nsi_pver == NDMP_V4)) {
npv3 = sp->nsi_data.nd_nlist.nld_dt_v3.dv3_nlist;
for (j = 0; j < sp->nsi_data.nld_nlist_len;
j++, npv3++) {
free(npv3->nn3_opath);
free(npv3->nn3_dpath);
}
free(sp->nsi_data.nd_nlist.nld_dt_v3.dv3_nlist);
}
}
free(*sinfopp);
*sinfopp = NULL;
}
/* ARGSUSED */
int
ndmp_get_stats(ndmp_stat_t *statp)
{
int opcode = NDMP_GET_STAT;
ndmp_door_err_t derr;
(void) mutex_lock(&ndmp_lock);
if (!statp) {
ndmp_errno = ENDMP_INVALID_ARG;
goto err;
}
if (ndmp_door_setup(opcode))
goto err;
if (ndmp_door_call(&derr))
goto err;
statp->ns_trun = ndmp_door_get_uint32(dec_ctx);
statp->ns_twait = ndmp_door_get_uint32(dec_ctx);
statp->ns_nbk = ndmp_door_get_uint32(dec_ctx);
statp->ns_nrs = ndmp_door_get_uint32(dec_ctx);
statp->ns_rfile = ndmp_door_get_uint32(dec_ctx);
statp->ns_wfile = ndmp_door_get_uint32(dec_ctx);
statp->ns_rdisk = ndmp_door_get_uint64(dec_ctx);
statp->ns_wdisk = ndmp_door_get_uint64(dec_ctx);
statp->ns_rtape = ndmp_door_get_uint64(dec_ctx);
statp->ns_wtape = ndmp_door_get_uint64(dec_ctx);
if (ndmp_door_fini())
goto err;
(void) mutex_unlock(&ndmp_lock);
return (0);
err:
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
int
ndmp_door_status(void)
{
int opcode = NDMP_GET_DOOR_STATUS;
ndmp_door_err_t derr;
(void) mutex_lock(&ndmp_lock);
if (ndmp_door_setup(opcode))
goto err;
if (ndmp_door_call(&derr))
goto err;
if (ndmp_door_fini())
goto err;
(void) mutex_unlock(&ndmp_lock);
return (0);
err:
(void) mutex_unlock(&ndmp_lock);
return (-1);
}
static int
ndmp_door_setup(int opcode)
{
/* Open channel to NDMP service */
if ((ndmp_door_fildes == -1) &&
(ndmp_door_fildes = open(NDMP_DOOR_SVC, O_RDONLY)) < 0) {
ndmp_errno = ENDMP_DOOR_OPEN;
return (-1);
}
switch (opcode) {
case NDMP_SHOW:
door_buf_size = (NDMP_DOOR_SESSION_INFO * ndmp_num_conn);
break;
case NDMP_DEVICES_GET_INFO:
door_buf_size = (NDMP_DOOR_DEVICE_INFO * ndmp_num_dev);
break;
default:
door_buf_size = NDMP_DOOR_SIZE;
}
/*
* No matter what, we don't allow our
* door size to be less than NDMP_DOOR_SIZE
*/
if (door_buf_size < NDMP_DOOR_SIZE)
door_buf_size = NDMP_DOOR_SIZE;
door_buf = malloc(door_buf_size);
if (door_buf == NULL) {
ndmp_errno = ENDMP_MEM_ALLOC;
return (-1);
}
enc_ctx = ndmp_door_encode_start(door_buf, door_buf_size);
if (enc_ctx == 0) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_DOOR_ENCODE_START;
return (-1);
}
ndmp_door_put_uint32(enc_ctx, opcode);
/*
* If it is an opcode that can elicit a variant
* response in size, tell the door callee
* how much memory to alloc when forming the
* response.
*/
switch (opcode) {
case NDMP_SHOW:
case NDMP_DEVICES_GET_INFO:
ndmp_door_put_uint32(enc_ctx, door_buf_size);
break;
}
return (0);
}
static int
ndmp_door_call(ndmp_door_err_t *derr)
{
uint32_t used;
int rc;
(void) memset((void *)derr, 0, sizeof (ndmp_door_err_t));
if ((ndmp_door_encode_finish(enc_ctx, &used)) != 0) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_DOOR_ENCODE_FINISH;
return (-1);
}
arg.data_ptr = door_buf;
arg.data_size = used;
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = door_buf;
arg.rsize = door_buf_size;
if (door_call(ndmp_door_fildes, &arg) < 0) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_DOOR_SRV_TIMEOUT;
(void) close(ndmp_door_fildes);
ndmp_door_fildes = -1;
return (-1);
}
dec_ctx = ndmp_door_decode_start(arg.data_ptr, arg.data_size);
rc = ndmp_door_get_uint32(dec_ctx);
if (rc != NDMP_DOOR_SRV_SUCCESS) {
derr->door_err = rc;
derr->door_extended_err = ndmp_door_get_uint32(dec_ctx);
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_DOOR_SRV_OPERATION;
return (-1);
}
return (0);
}
static int
ndmp_door_fini(void)
{
if ((ndmp_door_decode_finish(dec_ctx)) != 0) {
free(door_buf);
door_buf = NULL;
ndmp_errno = ENDMP_DOOR_DECODE_FINISH;
return (-1);
}
free(door_buf);
door_buf = NULL;
return (0);
}